Rendering react component after api response - javascript

I have a react component that I wish to populate with images using the Dropbox api. The api part works fine, but the component is rendered before the data comes through & so the array is empty. How can I delay the rendering of the component until it has the data it needs?
var fileList = [];
var images = [];
var imageSource = [];
class Foo extends React.Component {
render(){
dbx.filesListFolder({path: ''})
.then(function(response) {
fileList=response.entries;
for(var i=0; i<fileList.length; i++){
imageSource.push(fileList[0].path_lower);
}
console.log(imageSource);
})
for(var a=0; a<imageSource.length; a++){
images.push(<img key={a} className='images'/>);
}
return (
<div className="folioWrapper">
{images}
</div>
);
}
}
Thanks for your help!

Changes:
1. Don't do the api call inside render method, use componentDidMount lifecycle method for that.
componentDidMount:
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. Setting state in this method will
trigger a re-rendering.
2. Define the imageSource variable in state array with initial value [], once you get the response update that using setState, it will automatically re-render the component with updated data.
3. Use the state array to generate the ui components in render method.
4. To hold the rendering until you didn't get the data, put the condition inside render method check the length of imageSource array if length is zero then return null.
Write it like this:
class Foo extends React.Component {
constructor(){
super();
this.state = {
imageSource: []
}
}
componentDidMount(){
dbx.filesListFolder({path: ''})
.then((response) => {
let fileList = response.entries;
this.setState({
imageSource: fileList
});
})
}
render(){
if(!this.state.imageSource.length)
return null;
let images = this.state.imageSource.map((el, i) => (
<img key={i} className='images' src={el.path_lower} />
))
return (
<div className="folioWrapper">
{images}
</div>
);
}
}

You should be using your component's state or props so that it will re-render when data is updated. The call to Dropbox should be done outside of the render method or else you'll be hitting the API every time the component re-renders. Here's an example of what you could do.
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = {
imageSource: []
}
}
componentDidMount() {
dbx.filesListFolder({ path: '' }).then(function(response) {
const fileList = response.entries;
this.setState({
imageSource: fileList.map(file => file.path_lower);
})
});
}
render() {
return (
<div className="folioWrapper">
{this.state.imageSource.map((image, i) => <img key={i} className="images" src={image} />)}
</div>
);
}
}
If there are no images yet, it'll just render an empty div this way.

First off, you should be using the component's state and not using globally defined variables.
So to avoid showing the component with an empty array of images, you'll need to apply a conditional "loading" class on the component and remove it when the array is no longer empty.

Related

How to change the value in Constructor ReactJs

My problem is that the code is working correctly
I would like to be able to change the value val: 'yolo' by either a component from another page or direct by my database
Do you have an idea, how to fix this? Neff
import React from 'react'
import axios from 'axios'
const entrypoint = process.env.REACT_APP_API_ENTRYPOINT + '/api';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
this.clickHandler = this.clickHandler.bind(this)
this.state = {currentPosition: 0, totalLength: 3, val: 'yolo'}
}
getRandom = async () => {
const res = await axios.get(
entrypoint + "/alluserpls"
)
this.setState({ data: res.data })
}
componentDidMount() {
this.getRandom()
}
clickHandler(){
this.setState({currentPosition: (this.state.currentPosition + 1)%this.state.totalLength})
}
render() {
return (
<div >
<button onClick={this.clickHandler} >Move to the Right</button>
{
Array.from(
{length: this.state.totalLength},
(_,i) => (
<div key={i} className="slot">
<p>{i === this.state.currentPosition ? this.state.val : null}</p>
</div>
)
)
}
</div>
)}
}
export default App;
one way you can change the value of Yolo similar way as you are getting data from the server.
as for changing it from another component , you can do it by either getting it as a props from its parent component where you use this component
<App yoloVal = {"yoloValue"}/>
and you can receive it in props either when it mounts or when it updates
componentDidMount(){
this.setState({
yolo : this.props.yoloVal
}
}
or when it updates
componentDidUpdate(){
if(this.props.yoloVal !== prevProps.yoloVal){
this.setState({
yolo : this.props.yoloVal
}
}
}
you can also get this value from a child in the App by passing it a method
write a method in the App Component
setYoloValue(val){
this.setState({
yolo : val
}
}
now pass this method in render method of App to a child component
return (
<ChildComponent setYoloValue = {this.setYoloValue.bind(this)}
)
we are using bind so when this method is called the context remains of the parent instead of the caller(child component)
now you can use this method anywhere in the child to set the value of Yolo on parent
class ChildComponent extends Component {
componentDidMount(){
this.props.setYoloValue("new Yolo Value by child")
}
}
Now as for passing data between siblings , you can give the data by using the above two methods , first have a common parent , pass the data to parent by using second method then pass that data parent received to the other children as the first method. that is how you can acheive communication between siblings components.
as for setting the value from any other component in the app that is not directly related to you component , you need Redux or similar that does the job for you by keeping the values in a common store and components listen to that store and receive the update when the value in the store updates.
I would like to be able to change the value val: 'yolo'
1.by either a component,
2.from another page
3.or direct by my database
i'm actually surprised by the following piece of code, and not even sure, it 's a valid one. you are initializing this.state twice inside your constructor.
constructor(props) {
super(props);
--> this.state = {
data: [],
};
this.clickHandler = this.clickHandler.bind(this)
--> this.state = {currentPosition: 0, totalLength: 3, val: 'yolo'}
}
you initialize your entire variables inside your constructor..
constructor(props) {
super(props);
this.state = {
data: [],
currentPosition: 0,
totalLength: 3,
val: 'yolo',
};
this.clickHandler = this.clickHandler.bind(this)
}
idea is to pass a function(prevState) as a callback to update the local state so as to escape batching.
getRandom = async () => {
const res = await axios.get(
entrypoint + "/alluserpls"
)
this.setState(prevState => ({
...prevState,
data: res.data,
}))
}
i'm not sure this will work as you expected..
clickHandler(){
this.setState({currentPosition: (this.state.currentPosition + 1)%this.state.totalLength})
}
since you are doing a division, it's good to Math.floor or ceil(you need to find whichever value meets your requirement.)
//1. by a component..
handleValChange(val) => {
this.setState(prevState => ({
...prevState,
val,
}))
}
//now u can pass it to a child component.
render() {
const { handleValChange } = this
return (
<div>
<...rest of the div.../>
<ChildComponent {...{ handleValChange }} />
</div>
)
}
from another page.
from another page means, probable a diffrent route. in such cases u need to update this globally(redux, mobx etc..) and the value should also live globally not locally. u can pass id's and stuff via url but function, not possible.
direct by db.
this is where u make an api call and based on the response u update the state. that means, it's time to extract your application into a global state(redux, mobx etc..)
this.state = {
data: [],
};
this.clickHandler = this.clickHandler.bind(this)
this.state = {currentPosition: 0, totalLength: 3, val: 'yolo'}
You should not have two states in one constructor. Change it to one state:
this.state {
data: [],
currentPosition: 0,
totalLength: 3,
val: 'yolo',
}
As for changing the value from another component, there are two easy solutions.
1) Using Redux to handle state, instead of local state, probably the best solution.
2) Use a callback function that call setState in that component, and pass it to the other component, if it is a child of this component.
const myCallbackFunction(value: string) {
this.setState({ val: value })
}

creating component instances from array.map() and handing down functions

just a quick query.
I have an array of data points and using this to create instances of a component.
The parent component that creates an array of children components also have some functions I wish to hand down to its children. Is there any way this is possible? Check the code and let me know, I am getting errors.
class App extends React.Component {
constructor(props) {
super(props)
this.handleNext = this.handleNext.bind(this)
}
handleNext() {
// some function that uses setState and will cause component to re render
}
render() {
let children = someArray.map(function(elem, index) {
return (
<ChildComponent name = {elem.name} handDownFunc = {this.handleNext}/>
)
})
return (
{children} // I want them to each be able to access and use handleNext
)
}
}
You got this error, because using function keyword instead of arrow function.
For more infromation please follow this link.
So, it will be someArray.map(() => { return() })

shouldComponentUpdate() is not being called

Problem
I've parent class which contains list of items and renders component for each item of the list. When some item has been changed (even only one), all items in the list are being rerendered.
So I've tried to implement shouldComponentUpdate(). I am using console.log() to see if it is called but I can't see the log. I've found question shouldComponentUpdate is not never called and tried to return return (JSON.stringify(this.props) !=JSON.stringify(nextProps)); but component still renders itself again. So I've tried just to return false (like do not ever update) but it still does. As the last try I've used PureComponent but it is still being rerendered.
Question
How can I stop children re-rendering if the parent list changes and why is ShouldComponentUpdate never called?
Edit
I've noticed something what I didn't mention in question, I'm sorry for that. I am using context. If I don't use context -> it's ok. Is there any chance to stop re-render while using context? (I'm not using context on updated item - values of context didn't change).
Example
I've parent class which iterates list and renders TaskPreview component for each item of list:
class Dashboard extends React.Component
{
constructor(props) {
this.state = {
tasks: {},
};
}
onTaskUpdate=(task)=>
this.setState(prevState => ({
tasks: {...prevState.tasks, [task._id]: task}
}));
// ... some code
render() {
return (
<div>
{(!Object.entries(this.props.tasks).length)
? null
: this.props.tasks.map((task,index) =>
<TaskPreview key={task._id} task={task} onChange={this.onTaskUpdate}/>
})}
</div>
)
}
}
and I've children TaskPreview class:
class TaskPreview extends React.Component
{
shouldComponentUpdate(nextProps) {
console.log('This log is never shown in console');
return false; // just never!
}
render() {
console.log('task rendered:',this.props.task._id); // indicates rerender
return(<div>Something from props</div>);
}
}
TaskPreview.contextType = TasksContext;
export default TaskPreview;
As #Nicolae Maties suggested I've tried to use Object.keys for iteration instead of direct map but it still doesn't call "shouldComponentUpdate" and still being re-rendered even if there is no changes.
Updated code:
render() {
return (
<div>
{(!Object.entries(this.props.tasks).length)
? null
: Object.keys(this.props.tasks).map((key,index) => {
let task = this.props.tasks[key];
<TaskPreview key={task._id} task={task}/>
}
})}
</div>
)
}
Component is being re-rendered because of .contextType.
TaskPreview.contextType = TasksContext;
Also as is mentioned in documentation:
The propagation from Provider to its descendant consumers (including .contextType and useContext) is not subject to the shouldComponentUpdate method, so the consumer is updated even when an ancestor component skips an update. Source: reactjs.org/docs/context
You have to use context somehow else or do not use it at all.
You can use Context.Consumer which won't force re-render of current component but it might force re-render of its children.
<TasksContext.Consumer>
{value => /* render something based on the context value */}
</TasksContext.Consumer>
Instead of return (JSON.stringify(this.props) != JSON.stringify(nextProps)); in your shouldComponentUpdate() life cycle, try specifying tasks object like this return (JSON.stringify(this.props.tasks) != JSON.stringify(nextProps.tasks));
Maybe react is creating new instances of your component and replaces the old instances with them. That's why you're probably not getting your lifecycle method invoked. That can happen if the key property you're assigning in the map always changes.
use from pureComponent and array as state:
class Dashboard extends React.PureComponent
{
constructor(props) {
this.state = {
tasks: this.props.tasks
}
}
onTaskUpdate=(task)=>
this.setState(prevState => ({
tasks: [...prevState.tasks, task] // render only new task
}));
render() {
const {tasks} = this.state
return (
<div>
{tasks.map(task => <TaskPreview key={task._id} task={task} />)}
</div>
)
}
}
class TaskPreview extends React.PureComponent
{
render() {
console.log('task rendered:',this.props.task._id); // indicates rerender
return(<div>Something from props</div>);
}
}
In the shouldComponentUpdate() method of your TaskPreview component, you should check if the next props have changes in comparison to the current props. Then if there are changes, return true to update the component, otherwise false.
The following example compares all the fields of props object with the new props object. But you can only check the props you are interested in.
shouldComponentUpdate(nextProps) {
return !!(Object.keys(nextProps).find(key => nextProps[key] !== this.props[key]));
}
I tried with below code snippet, shouldComponentUpdate worked as I expected. Could you share your Dashboard initial props ?
class Dashboard extends React.Component {
constructor(props) {
this.state = {
tasks: {}
};
}
onTaskUpdate = task =>
this.setState(prevState => ({
tasks: { ...prevState.tasks, [task._id]: task }
}));
// ... some code
render() {
return (
<div>
{!Object.entries(this.props.tasks).length
? null
: Object.keys(this.props.tasks).map((key, index) => {
let task = this.props.tasks[key];
return (
<TaskPreview
key={task._id}
task={task}
onChange={this.onTaskUpdate}
/>
);
})}
</div>
);
}
}
class TaskPreview extends React.Component {
shouldComponentUpdate(nextProps) {
console.log("This log is never shown in console");
return nextProps.task._id != this.props.task._id;
}
render() {
console.log("task rendered:", this.props.task); // indicates rerender
return (
<button onClick={() => this.props.onChange(this.props.task)}>
Something from props
</button>
);
}
}
my initial props for Dashboard component is :
<Dashboard tasks={{test:{_id:'myId', description:'some description'}}}/>

Auto Update value in react component

I'm trying to build a component with auto-updating value based on cookies:
let cookies = 0;
(function count() {
cookies = document.cookie.split("?");
setTimeout(count, 10);
return cookies;
})();
class CartButton extends React.Component {
state = {quantity: cookies.length}
render() {
return (
<Cart onClick={e=>{show_cart()}}>
<Mfont>{this.state.quantity}</Mfont>
<Icon>shopping_cart</Icon>
</Cart>
);
}
}
'count' function works as expected, component is rendered with the latest value returned. Unfortunately, it does not auto-update when 'cookies' are changed. It returns this error:
Warning: render(...): Replacing React-rendered children with a new root component. If you intended to update the children of this node, you should instead have the existing children update their state and render the new components instead of calling ReactDOM.render.
I have tried various variations here but still can't figure it out :/
componentDidMount will get execute only once when your component loads first time. This is the correct place to write any logic which we need to execute after page load.
Try this,
class CartButton extends React.Component {
//It is good to have a constructor for the component which has state
constructor(props){
super(props);
this.state = {quantity: cookies.length}
this.updateQuantity;
}
componentDidMount(){
this.updateQuantity = setInterval(()=> {
cookies = document.cookie.split("?");
this.setState({quantity: cookies.length})
},10)
}
//Don't forget to clear any setInterval like below
componentWillUnmount(){
clearInterval(this.updateQuantity);
}
render() {
return (
<Cart onClick={e=>{show_cart()}}>
<Mfont>{this.state.quantity}</Mfont>
<Icon>shopping_cart</Icon>
</Cart>);
}
}
Here your CartButton is not updating even though count is working fine because CartButton is not listening to your cookies variable. React component updates only when there is either props or state change.
You can something like this..
class CartButton extends React.Component {
state = {quantity: cookies.length}
componentDidMount(){
setInterval(function count() {
cookies = document.cookie.split("?");
this.setState({quantity: cookies})
}.bind(this), 10)
}
render() {
return (
<Cart onClick={e=>{show_cart()}}>
<Mfont>{this.state.quantity}</Mfont>
<Icon>shopping_cart</Icon>
</Cart>);
}
}

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

Categories

Resources