ReactJS 0.14 - Working with promises - javascript

OK, I am advancing my project from having simple data arrays in the component to an API call for the data, and as things always are in life, the component no longer works. The API is working I can see the data results, but it is just not producing them in the render:
// function
showMainTestimonial(clientId) {
let results = [];
let linkButtonNode = null;
TestimonyApi.getAll()
.then(function (data) {
var data = data.data;
if (data.showMore) {
linkButtonNode = (
<div style={{ margin:15, width:'100%', textAlign:'center' }}>
<Link className="button" to={ data.testimonialsLink }>More Testimonials</Link></div>)
}
data.testimonials.map(function (testimony, index) {
let commenter = null;
let category = null;
if (testimony.isMain && results.length === 0) {
if (data.showCommenterName) {
commenter = (
<div style={{ textAlign: 'right', fontSize: 16 }}>
<i>-- { testimony.commenter }</i>
</div>
);
}
if (testimony.category) {
category = (
<div style={{ textAlign: 'right', fontSize: 16 }}>
<i> { testimony.category }</i>
</div>
);
}
results.push(
<div id="TestimonialArea" key={ index }>
<div className="main" id="TestimonialZone">
<div id="TestimonialsFeed" className="NavFeed">
<div className="testspcr"></div>
<article className="lists">
<h3>
"{ testimony.title }"
</h3>
<div className="ReviewText">
"{ testimony.comment }"
</div>
{ commenter }
{ category }
</article>
{ linkButtonNode }
</div>
</div>
</div>
)
console.log('results from function: ' + JSON.stringify(results))
return results;
}
});
}.bind(this))
}
// render
render() {
let clientId = this.props.clientId;
var results = this.showMainTestimonial(clientId);
console.log('render results: ' + results);
return (
<section>
{ results }
</section>
)
}
As you can see the data is there, I am just doing something STUPID.
Any ideas?
Thanks in Advance.

You need to take the result of the promise and put it in state, and then have render be based on state.
class Foo extends React.Component {
constructor(){
super();
this.state = {data: null};
}
fetchTestimonial(clientId) {
TestimonyApi.getAll()
.then((x) => this.setState({data: x}))
}
render(){
if (!this.state.data) return <div>Loading</div>
return (
<div>
{this.state.data.map(f)}
</div>
)
}
}
Note: the arrow function is important, it ensures this is correct in the .then callback. Consider only using arrow functions inside methods because it avoids an entire type of bug.

TestimonyApi.getAll / promises are asynchronous. This is a very common problem for people new to JavaScript and has been extensively discussed:
How do I return the response from an asynchronous call?
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
In the context of React, the data should probably be part of the component's state. Instead of returning results (into the void), update the state instead:
this.setState({results});
This will cause the component to rerender. Inside the render method, access this.state.results instead.
You can start the fetching "automatically" by calling the method after the component was mounted. See componentDidMount in Lifecycle Methods.
FYI, the render method should never really fetch data. It is called after the data has changed (either because the component's props or state changed). Maybe reading Thinking in React will help you get a better understanding of this.

I figured out my problem, and refactoring the code makes it clearer to understand.
Basically as FakeRain said, the arrow function was key, although I guess I don't work well with general prose / high altitude theory. No matter, for anyone else struggling.
When you fetch data, and then plan to map it into a specific html 'fragment', split up the fetching and the mapping into 2 different functions, and remember to:
Use the arrow function in the API fetching; (because of 'this')
Call the mapping function in the API function; (because the API call is asynchronous)
Bind the mapping function;
Pass the results of your mapping to state.
So, illustratively speaking:
componentDidMount() {
this.getAPIData();
}
getAPIData() {
TestimonyApi.getAll()
.then((response) => { <-- don't forget the arrow function
let data = response.data;
// call the mapping function inside the API function
this.showMainTestimonial(data)
})
}
showMainTestimonial(data) {
let results = [];
data.map(function(item) {
// do something with mapped data and push in results
results.push (
// customizing the mapping into whatever results you want
)}.bind(this)) <--- don't forget to bind
// pass your configured html into state now after mapping
this.setState({results});
}

Related

Re render child component depending on child action while passing data to parent in React

I have a Parent Component where I make an Ajax call to hydrate two children Components. C1 only needs the data once, C2 actually is able to get more data through additional Ajax calls and will be re render adequately .
It is way easier for me to make the Ajax Call in my Parent Component. But then I can't figure out how to 'get more data' in C2, pass that data in Parent and finally re render C2 only. I had many tries through callbacks (you can see it in the code), lifting state up etc, I am unable to do it. I don't know what to do with useEffect either...
Some code :
const Reviews: FunctionComponent = () => {
let variables = {
offset: 0,
limit: 2,
filter: filter,
order: order
}
useEffect(() => {
console.log("useEffect called");
});
const {data: dataReviews, loading: loadingReviews, error: errorReviews} = useQuery(GetReviews, {
ssr: false,
variables: variables
});
function parentCallback(offset: number, limit: number) {
variables.offset = offset;
variables.limit = limit;
}
if (!loadingReviews && !errorReviews && dataReviews) {
let reviews = !loadingReviews && !errorReviews && dataReviews ? dataReviews.reviews[0] : null;
const stats = reviews.stats;
reviews = reviews.reviews;
return (
<div className={`${styles.netreviews_review_rate_and_stars}`}>
<ReviewsSideInfo stats={stats}
total={getRecommandation(stats).total}
recommandation={getRecommandation(stats).percentageRecommandation}
/>
<ReviewsContainer parentCallback={parentCallback} reviews={reviews}/>
</div>
);
}
return (<div></div>);
}
export default Reviews;
As you can see <ReviewsSideInfo/> (C1) relies on that Ajax call (using stats object), and so does <ReviewsContainer /> (C2), but in C2 -> :
const ReviewsContainer: FunctionComponent<ReviewsContainerProps> = ({reviews, parentCallback}) => {
const [{offset, limit}, setReviews] = useState({offset: 0, limit: 3});
return (
<div className={`${styles.right_block}`}>
<div className={`${styles.reviews_list}`}>
<Fragment>
{reviews.map((element, i) => {
return (
<div>
<div key={i}>
<Review rate={element.rate}
firstname={element.firstname}
lastname={element.lastname}
review={element.review}
/>
</div>
</div>
)
})}
<button onClick={() => {
setReviews({offset: 0, limit: limit + 3});
parentCallback(0, limit + 3)
}}>
<FormattedMessage id="load-more"/></button>
</Fragment>
</div>
</div>
)
}
-> I need to be able to load more data.
Sorry for the long piece of code. How can I achieve that ? I know useReducer may be of help but it seems even more difficult to me since I'm new to React.
Last thing : in C1, I need to be able to filter that data (per rating) so I need to find a way to communicate through all these components... same problem actually.
EDIT: I could have updated the state in Parent component but then the whole component would refresh and that's not what I want (already tried that)
Did you try wrapping your component with
React.memo(your component)
I will look something like below:
const MemoisedComponent = React.memo(ReviewsSideInfo);
<div className={`${styles.netreviews_review_rate_and_stars}`}>
<MemoisedComponent stats={stats}
total={getRecommandation(stats).total}
recommandation={getRecommandation(stats).percentageRecommandation}
/>
<ReviewsContainer parentCallback={parentCallback} reviews={reviews}/>
</div>
Also in
parentCallback(offset,limit) {
setOffset(offset)
setLimit(limit)
}
below useEffect will only be called when the offset and limit value is changed.
useEffect(() => {
//api call logic
},[offset,limit]);
below is how your state should look like:
const [offset,setOffset] = useState(0)
const [limit,setLimit] = useState(3)
...and so on

How to avoid using setState in render method?

I am using react-apollo to fetch data through <Query /> and <Mutation />.
Thus, I want to setState when I get some data. I am getting the data in the render method.
Like this:
render() {
return (
<Query query={CAN_UPDATE_POST_QUERY} variables={{ id: this.props.router.query.postId }}>
{ payload => {
if(payload.loading) {
<div style={{width: '98%', textAlign: 'center', maxWidth: '1000px', margin: '50px auto'}}>Loading...</div>
}
if(this.isNew()){
return (
<PleaseSignIn>
{ me => (
...something
) }
</PleaseSignIn>
)
} else if (payload.data && payload.data.canUpdatePost) {
// I get payload here. Here's where I want to set new state.
this.canUpdatePost = payload.data.canUpdatePost
this.setState({ canUpdatePost: this.canUpdatePost })
return (
<PleaseSignIn>
{ me => (
...something
) }
</PleaseSignIn>
)
} else {
return (
<div style={{width: '98%', textAlign: 'center', maxWidth: '1000px', margin: '50px auto'}}>You and your mind seems to be lost. šŸ”</div>
)
}
} }
</Query>
)
}
Using setState in render gives me this error:
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
How do I think in React way? And especially, how do I get my state changed when I get payload from react-apollo?
NEWBIE HERE. Sorry if silly.
Thanks in advance. :-)
In general, you should avoid using setState in your render functions. You should avoid having side affects (such as setting state) in your render function and instead should call other component functions to handle data change in your component.
The render() function should be pure, meaning that it does not modify component state, it returns the same result each time itā€™s invoked, and it does not directly interact with the browser.
See the render() method reference in the react docs: https://reactjs.org/docs/react-component.html#render
You can create a function outside of your render method to fix this problem like so:
YourComponent extends React.Component {
handleCanUpdatePost = (canUpdatePos) => {
this.setState({ canUpdatePost })
}
render() {
// Your render logic here
// Whenever you want to setState do so by
// calling this.handleCanUpdatePost(payload.data.canUpdatePost)
}
}
You should also probably have a check to see if the value of state is going to change before setting it to avoid unnecessary re-renders:
handleCanUpdatePost = (canUpdatePos) => {
this.setState((state) => {
if(canUpdatePos !== state.canUpdatePost) {
return {canUpdatePost: payload.data.canUpdatePost}
}
})
}

Adding child elements dynamically in React (state array)

I am working to build a Pokedex from JSON data in React. I am refactoring this project from one I built in jQuery, so it could be that the jQuery approach is causing me to misunderstand how to approach this problem with proper React thinking. What's tripping me up so far is how to dynamically render multiple child elements based on the JSON I pass from a the parent element (this would be jQuery append).
Here is my App.js code:
class App extends Component {
render() {
return (
<div className="App background">
<div className="content">
<Header />
<TilesContainer pokedexName="national"/>
</div>
</div>
);
}
The TilesContainer essentially receives the name of a Pokedex and makes a call to an API. The individual Pokemon names are stored in an array in the TilesContainer state (this.state.pokemon), as below.
class TilesContainer extends Component {
constructor(props){
super(props);
this.state = {pokemon: []};
this.getPokemon = this.getPokemon.bind(this);
this.tiles = this.tiles.bind(this);
}
getPokemon() {
// set this.state.pokemon to the list
let link = 'https://pokeapi.co/api/v2/pokedex/' + this.props.pokedexName + '/';
fetch(link)
.then(response => response.json())
.then(myJson => {
let list = myJson['pokemon_entries'];
list.forEach(pokemon => {
this.state.pokemon.push(pokemon);
})
})
this.tiles();
}
tiles() {
if (this.state.pokemon.length > 0) {
return (
this.state.pokemon.map(pokemon => {
<Tile number={pokemon.entry_number}/>
})
)
}
}
render(){
this.getPokemon();
return (
<div id="tiles-container"
className="tiles-container">
<h1>TilesContainer Test</h1>
<Tile number={1} />
</div>
)
}
}
export default TilesContainer
Again, the idea is that a Pokemon tile is render for each Pokemon in the Pokedex JSON (which for now I've stored in this.state.pokemon - not sure if this is the best approach). I found an example here on Stack Overflow that uses an additional function (this this case this.tiles() to generate what I think is an array of returns with different child elements). The <Tile number={1} /> is a hardcoded example of how the tile is called.
Currently no dynamically-rendered tiles show up when the code runs. Is this the correct approach. I'd really appreciate any suggestions.
Thanks!
It looks like you're almost there.
First off, never modify state directly. Use this.setState() instead. State in React is updated asynchronously. For your purposes, you should be able to modify getPokemon() like the following. I also removed the this.tiles() call, as it is unnecessary.
getPokemon() {
// set this.state.pokemon to the list
let link = 'https://pokeapi.co/api/v2/pokedex/' + this.props.pokedexName + '/';
fetch(link)
.then(response => response.json())
.then(myJson => {
let list = myJson['pokemon_entries'];
this.setState({
pokemon: list,
});
})
}
A minor correction for tiles(): when using an arrow function and returning something in one line, use parentheses instead of curly braces. When you use curly braces, you have to include a return statement. With parentheses, you do not.
tiles() {
if (this.state.pokemon.length > 0) {
return (
this.state.pokemon.map(pokemon => (
<Tile number={pokemon.entry_number}/>
))
)
}
}
Next, since tiles() returns your dynamic tile components, it needs to be included in what you return in render().
render(){
return (
<div id="tiles-container"
className="tiles-container"
>
<h1>TilesContainer Test</h1>
{this.tiles()}
</div>
)
}
Lastly, I think the call to this.getPokemon() would make more sense in the constructor, rather than in render().
I think your method of getting the json data and storing it in state is fine, by the way. In the future, you may want to look into Redux to manage your state, but it could be overkill for a really small application.
so you are passing the pokedexName from the parent component which is app.js, once you get the props you can call the rest api call on the componentWillMount life cycle.
so on the render since the api call has been initiated it wont have any data thats why we are using a ternary operator to check the array once the api call get finished and we get the data we are setting the data to the pokemon array.
Since the state is updated react will automatically render a re render so the data will appear.
i hope the below code will solve the issue, please let me know :)
// App.js
import React, { Component } from 'react';
import TilesContainer from './components/TileContainer/TilesContainer'
class App extends Component {
render() {
return (
<div>
<TilesContainer pokedexName="national" />
</div>
);
}
}
export default App;
// Tiles container
import React, {Component} from 'react';
import axios from 'axios';
class TilesContainer extends Component{
//state
state ={
pokemon: []
}
// life cycle methods
componentWillMount(){
let link = 'https://pokeapi.co/api/v2/pokedex/' + this.props.pokedexName + '/';
axios.get(link)
.then(res => {
this.setState({
pokemon: res.data["pokemon_entries"]
})
})
}
render(){
let style ={display:"inline"}
return(
<div>
{
this.state.pokemon.length > 0 ?
this.state.pokemon.map(pokemon => {
return(
<div key={pokemon.entry_number}>
<p style={style}>{pokemon.entry_number}</p>
<a href={pokemon.pokemon_species.url}>{pokemon.pokemon_species.name}</a>
</div>
)
})
:
null
}
</div>
)
}
}
export default TilesContainer

How do I create an array after Axios response and Display it in React js

I am having trouble creating an array of titles from an Axios response. The method getTitles(props) receives data from the Axios response. How do I create an array of titles dynamically?
The functions I have tried in Javascript are for loops and EC6 mapping, nothing seems to work. Being new to react I could be missing something but I am not sure what it is.
React code
export default class Featured extends React.Component {
constructor() {
super();
this.state = {
data: null,
}
}
/**
* Received request from server
*/
componentDidMount(){
ApiCalls.articleData()
.then(function(data){
this.setState(function(){
return {
data: data
}
})
}.bind(this));
}
getTitles(props){
//-- What code do I place here?
console.log(props.data)
return ['test title', 'test title 2'];
}
/**
* Render request
*/
render() {
let dataResponse = JSON.stringify(this.state.data, null, 2);
const Articles = this.getTitles(this.state).map((title, i) => <Article key={i} title={title}/> );
return (
<div class="row">{Articles}
<pre>{dataResponse}</pre>
</div>
);
}
}
Axios Code
var ApiCalls = {
articleData: function(id){
return axios.all([getArticles(id)])
.then(function(arr){
return arr[0].data.data;
})
.catch(function (error) {
console.log(error);
})
},
React setState behaves asynchronously . Your articles get rendered before the ajax was called and was not re rendered due to asynchrony in setState.
This is what doc(https://reactjs.org/docs/react-component.html#setstate) says
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
You can render the article after successful ajax call like below
componentDidMount(){
ApiCalls.articleData()
.then(function(data){
render(<Article data={data}/>, document.getElementById('...'));
}.bind(this));
}
Because of the post above, I was able to fix the issue by the code example below To see a working example goto the git repo
ApiCalls.articleData()
.then(function(data){
const newData = data.map(c => {
return c.attributes.title;
})
const addElement = newData.map((title, i) => <ContentTiles key={i} title={title}/> );
const newState = Object.assign({}, this.state, {
newData: addElement
});
this.setState(newState);
}.bind(this));

Rendering an array of objects React

Good afternoon,
I am trying to display data that is provided to my application by an instance of MongoDB before the initial render. I have yet been successful in doing so either running into errors or warnings.
This is the part of the render method I am working with.
<div className="right-column">
<div className="pipeline">
{this.props.tournamentList.map((x,i) => {
return <li key={i}>{x.name}</li>
})}
</div>
</div>
this.props.tournamentList has a value of an array of objects like so:
tournamentList:
Array[15]
0:
{ā€¦}
1:
{ā€¦}
2:
{ā€¦} ...
This list comes to my application through the componentWillMount lifecycle method, so before the initial render. To me I should be able to iterate through the array and make a dynamically generated list of tournaments provided by my database.
Yet with the code I provided I am getting this warning:
Uncaught (in promise) Error: Objects are not valid as a React child (found: object with keys {prelims, outRounds, notes}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method ofBuildTournament.
I tried this approach, creating called displayTournaments and calling it inside the div with the class of "pipeline" but nothing happens, no errors no render:
displayTournaments(){
const { tournamentList } = this.props;
tournamentList.map((x,i) => {
return <li key={i}>{x.name}</li>
})
}
Clearly I am doing something wrong but I don't know what. Is this an instance where I should be using keyed fragments as suggested by the error message? Would anyone smarter than myself be willing to lend some insight?
Cheers.
Update:
Sorry, I misunderstood your question. Kyle is correct with the loading state.
In addition, using a library like lodash will allow you to map over objects in a more natural manner. The native javascript map method doesn't handle objects all that well.
https://www.npmjs.com/package/lodash
you use it much the same way. just
import _ from lodash
then
_.map(objectToMap, (x) => <Component key={x}>{x.thing}</Component>)
Here would be a simple solution that would have a loading state, error state, and success state.
The first thing to note is you will need to use Object.keys() to your object in order to map over the array of keys since you cannot map plain objects. You should also note that the map will return the key of each object so in order to target key values pairs you will need to use a syntax like this tournaments[key].name rather than just doing tournament.name as you are targeting an object with in an object and then grabbing the value.
Let me know if you need any more help with this
import React from 'react'
import Loader from '../Loader'
const resultList = ({ tournaments, isFetching = true }) => (
<div>
{
isFetching
? <div>
<Loader /><br />
<span>Loadingā€¦</span>
</div>
: <div>
{
Object.keys(tournaments).length
? <div>
{
tournaments.map((key) => (
<section id={tournaments[key].id} key={key}>
<p>{tournaments[key].name}</p>
</section>
))
}
</div>
: <div>
<p>There are no tournaments....</p>
</div>
}
</div>
}
</div>
);
export default resultList
You are going to need to have a loading state if you get your data in the componentWillMount or componentDidMount lifecycle hooks. The below example will illustrate how this is done.
class ComponentThatGetsAsyncData extends PureComponent {
constructor( props ) {
super( props );
this.state = {
tournamentList: [ ]
}
}
componentDidMount() {
// use any http library you choose
axios.get( "/some_url" )
.then( ( res ) => {
// this will trigger a re-render
this.setState({
tournamentList: res.data
});
})
.catch( ( err ) => {
// handle errors
});
}
render() {
const { tournamentList } = this.state;
// i'd use something other than index for key
// on your initial render your async data will not be available
// so you give a loading indicator and when your http call
// completes it will update state, triggering a re-render
return (
{
tournamentList ?
tournamentList.map((x,i) => {
return <li key={i}>{x.name}</li>
}) :
<div className="loading">Loading...</div>
}
);
}
}

Categories

Resources