Prevent reinitialization of React tooltip component to prevent API calls - javascript

I load profile data from API to be displayed when the client hovers over a profile name. When the client stops hovering and then hovers again, the tooltip component gets reinitialized and an API call is done again. Currently I am using localStorage to cache the data but this seems like an improper fix.
My question is how do I prevent the tooltip component from being thrown away and reinitialized?
const renderTooltip = (props) => (
<Tooltip>
<UserInfoFetchData uuid={props} />
</Tooltip>
);
<OverlayTrigger
placement="top"
uuid={gamer.uuid}
delay={{ show: 250, hide: 400 }}
overlay={renderTooltip(gamer.uuid)}
>
<a href={"/user/" + gamer.uuid}>{gamer.username}</a>
</OverlayTrigger>
The above code is used to call the React Component. Below you see the code of the component itself.
import React, { useEffect, useState } from "react";
import Backend from "../../data/Backend";
export default class UserInfoFetchData extends React.Component {
constructor(props) {
super(props);
console.log("test");
this.state = {};
}
componentDidMount() {
const cacheLocation = "profile " + this.props.uuid;
let data = localStorage.getItem(cacheLocation);
if (!data) {
this.controller = new AbortController();
new Backend()
.getUser({ uuid: this.props.uuid }, { signal: this.controller.signal })
.then((response) => {
console.log("fetched data");
this.setState(response.data);
localStorage.setItem(cacheLocation, JSON.stringify(response.data));
this.controller = null;
});
} else {
this.setState(JSON.parse(data));
}
}
componentWillUnmount() {
if (this.controller) {
this.controller.abort();
}
}
render() {
const capitalize = (str) => {
return `${str[0].toUpperCase()}${str.slice(1)}`;
};
return (
<div className="card border-dark ">
<div className="card-body">
<table className="table table-hover">
<tbody>
{Object.keys(this.state)
.filter(
(o) =>
o !== "uuid" &&
o !== "username" &&
!o.includes("weekly") &&
!o.includes("monthly")
)
.map((e, i) => {
return (
<tr key={e}>
<th scope="row">{capitalize(e.replace("_", " "))}</th>
<td>{this.state[e]}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}
}
The console logs "test" each time I hover over a username, which means the component is being reinitialized each time. Is there a way I can make this only initialize once? Or is there a way to properly store the API retrieved data in the class?
Note: new Backend().getUser() simply returns an AxioInstance.

Related

Struggling to pass an index of an array as props to another component

I am trying to build an app in which a user can add a card to an array of cards, then switch the positions of a specific card with the card to the left or right. I wrote a function that I believe will switch a card with that on the left, but I am struggling to debug it because it seems that the index of the chosen card is not properly being passed down to the child component.
Here is my code so far:
CardList.js is what is attempting to pass the moveLeft method to cardItem
import React from "react";
import CardItem from "./CardItem";
import CardForm from "./CardForm";
import './Card.css';
class CardList extends React.Component {
state = {
cards: JSON.parse(localStorage.getItem(`cards`)) || []
// when the component mounts, read from localStorage and set/initialize the state
};
componentDidUpdate(prevProps, prevState) { // persist state changes to longer term storage when it's updated
localStorage.setItem(
`cards`,
JSON.stringify(this.state.cards)
);
}
render() {
const cards = this.getCards();
const cardNodes = (
<div style={{ display: 'flex' }}>{cards}</div>
);
return (
<div>
<CardForm addCard={this.addCard.bind(this)} />
<div className="container">
<div className="card-collection">
{cardNodes}
</div>
</div>
</div>
);
}
addCard(name) {
const card = {
name
};
this.setState({
cards: this.state.cards.concat([card])
}); // new array references help React stay fast, so concat works better than push here.
}
removeCard(index) {
this.state.cards.splice(index, 1)
this.setState({
cards: this.state.cards.filter(i => i !== index)
})
}
moveLeft(index, card) {
this.setState((prevState, prevProps) => {
return {cards: prevState.cards.map(( c, i)=> {
// also handle case when index == 0
if (i === index) {
return prevState.cards[index - 1];
} else if (i === index - 1) {
return prevState.cards[index];
}
})};
});
}
//moveRight(index, card) {
// ?
// }
getCards() {
return this.state.cards.map((card) => {
return (
<CardItem
card={card}
index={card.index}
name={card.name}
removeCard={this.removeCard.bind(this)}
moveLeft={this.moveLeft.bind(this)}
// moveRight={this.moveRight}
/>
);
});
}
}
export default CardList;
cardItem is struggling to find the index of the necessary card even though I passed that in as props. I am getting an error saying "×
TypeError: Cannot read property 'index' of undefined" originating from my CardList component.
import React from 'react';
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";
class CardItem extends React.Component {
render() {
return (
<div>
<Card style={{ width: '15rem'}}>
<Card.Header as="h5">{this.props.name}</Card.Header>
<Card.Body>
<Button variant="primary" onClick={this.handleClick.bind(this)}>Remove</Button>
</Card.Body>
<Card.Footer style={{ display: 'flex' }}>
<i class="arrow left icon" onClick={this.leftClick.bind(this)} style={{ color: 'blue'}}></i>
{/*<i class="arrow right icon" onClick={rightClick(index, card)} style={{ color: 'blue'}}></i>*/}
</Card.Footer>
</Card>
</div>
)
}
handleClick(index) {
this.props.removeCard(index)
}
leftClick(index, card) {
this.props.moveLeft(index,card)
}
rightClick(index, card) {
this.props.moveRight(index, card)
}
}
export default CardItem;
How can I best pass down the necessary index as props? Thank you
Edit #1
I made an error in my addCard method, I never assigned the index to the card. I have fixed this and added a key property in my map return function but am now getting an error saying "×
TypeError: Cannot read property 'name' of undefined"
Please see the updated CardList.js below:
import React from "react";
import CardItem from "./CardItem";
import CardForm from "./CardForm";
import './Card.css';
class CardList extends React.Component {
state = {
cards: JSON.parse(localStorage.getItem(`cards`)) || []
// when the component mounts, read from localStorage and set/initialize the state
};
componentDidUpdate(prevProps, prevState) { // persist state changes to longer term storage when it's updated
localStorage.setItem(
`cards`,
JSON.stringify(this.state.cards)
);
}
render() {
const cards = this.getCards();
const cardNodes = (
<div style={{ display: 'flex' }}>{cards}</div>
);
return (
<div>
<CardForm addCard={this.addCard.bind(this)} />
<div className="container">
<div className="card-collection">
{cardNodes}
</div>
</div>
</div>
);
}
addCard(name, index) {
const card = {
name,
index
};
this.setState({
cards: this.state.cards.concat([card])
}); // new array references help React stay fast, so concat works better than push here.
}
removeCard(index) {
this.state.cards.splice(index, 1)
this.setState({
cards: this.state.cards.filter(i => i !== index)
})
}
moveLeft(index, card) {
this.setState((prevState, prevProps) => {
return {cards: prevState.cards.map(( c, i)=> {
// also handle case when index == 0
if (i === index) {
return prevState.cards[index - 1];
} else if (i === index - 1) {
return prevState.cards[index];
}
})};
});
}
//moveRight(index, card) {
// ?
// }
getCards() {
return this.state.cards.map((card) => {
return (
<CardItem
card={card}
key={card.index}
name={card.name}
removeCard={this.removeCard.bind(this)}
moveLeft={this.moveLeft.bind(this)}
// moveRight={this.moveRight}
/>
);
});
}
}
export default CardList;
There is a problem with your addCard & removeCard functions. State updates may be asynchronous in React, due to which you should not use this.state inside this.setState.
Eg: addCard should be as follows:
addCard(name, index) {
let card = {name,index};
this.setState((prevState, prevProps)=> {
return prevState.cards.concat(card);
})
}
removeCard should be modified likewise. The splice should be removed too, as the filter does the removing.
removeCard(index) {
this.setState(function(prevState,prevProps) {
return {cards: prevState.cards.filter(function(card,i) {
return i != index;
})};
});
}

Unable to render data from API in React, no errors displayed

I am using Django Rest Framework to send data to React app. But the data is being shown on screen.
The code isnt returning any errors thus making it difficult to see whats going wrong. This is my second React project thus i am not too familiar with React & JS as of now.
This is my code:
import { render } from "#testing-library/react";
import axios from "axios";
import React, { Component, useState, useEffect } from "react";
const api_url = "http://localhost:8000/api/CBView/"
class StocksHomePage extends Component {
constructor(props) {
super(props);
this.state = {
isFetching:false,
data_s :[]
};
}
componendDidMount() {
this.fetchData();
this.timer = setInterval(() => this.fetchData(), 50);
}
fetchData = () => {
this.setState({...this.state, isFetching:true});
axios.get(api_url)
.then (response => {
this.setState({data_s:response.data[0]})
})
.catch(e => {
console.log(e);
this.setState({...this.state, isFetching:false});
});
};
render() {
return (
<div>
{this.state.data_s.map(m => <p>{m.co_S}</p>)}
{/* <p data={this.state.data_s.co_S} ></p> */}
<ul>
<li isKey dataField='co_N'></li>
<li dataField='co_S'></li>
<li dataField='price'></li>
</ul>
<p>{this.state.isFetching ? 'Fetching users...' : ''}</p>
</div>
)
}
}
I fixed the issue, all i had to do was include maps function with variable to represent those values. Here is my code:
class StocksHomePage extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
loaded: false,
placeholder: "loading"
};
}
componentDidMount() {
axios
.get("http://localhost:8000/CBView")
.then(response => {return response.data; })
.then(data => {this.setState(() => {
return {data, loaded:true};
});
});
}
handleClick = (props) => {
<HashRouter>
<Route path='/StocksDetail' component={StocksDetail} />
</HashRouter>
};
render() {
return (
<table className="table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Price/ Chng</th>
<th>Mkt Cap</th>
<th>Volume</th>
<th>Turnover</th>
</tr>
</thead>
<tbody>
{this.state.data.map(item => {
return (
<tr key={item.co_S}>
<button onCLick={this.handleClick(item.co_S)}><td >{item.co_N}</td></button>
<td>{item.price}</td>
<td>{item.p_chng_pc}</td>
<td>{item.Mkt_cap}</td>
<td>{item.volume}</td>
<td>{item.volume * item.price}</td>
</tr>
);
})};
</tbody>
</table>
);
}
}
export default StocksHomePage;

How to pass argument from functional component to class component

EDIT - I fixed this and posted the working code.
I'm working on a project and I am having a specific issue I can't figure out how to fix. I am displaying a list of champions images and when the user clicks on one of them (s) then it will change the page to display that champions name. Currently I can console.log any of the names without any issues which means my functional component Newchamp() is working! However I am having trouble passing an argument from NewChamp to the class component SpecificChamp. When I add the last line in Newchamp return and try to display it in SpecificChamp using {s} its undefined!
Is it possible to pass an argument from my functional class to my component class? if not how can I get the page to change to the specific image that is clicked? I am new to react and appreciate any help!
Can anyone please help me out with this
import React, { Component } from 'react';
import './Champions.css';
class AllChamps extends Component {
render() {
let champion = this.props.champion;
return(
<div className='champions'>
<h1> all champions</h1>
{Object.keys(this.props.champions).map((s) => (
<div className='champs' onClick={() => this.props.NewChamp({s, champion})}>
<img
alt='Champion Images'
src={`http://ddragon.leagueoflegends.com/cdn/10.16.1/img/champion/${s}.png`}
onClick={this.props.onClick}
></img>
{s}
</div>
))}
</div>
)}}
class SpecificChamp extends Component {
render() {
let champion = this.props.champion
let Spec = champion[champion.length - 1];
return (
<div className='champions'>
<h1> 1 champions</h1>
<div className='champs'>
<button onClick={this.props.onClick}></button>
{Spec}
</div>
</div>
)}
}
class Champions extends Component {
constructor(props) {
super(props);
this.handleAllChamps = this.handleAllChamps.bind(this);
this.handleSpecificChamp = this.handleSpecificChamp.bind(this);
this.NewChamp = this.NewChamp.bind(this);
this.state = {
champions: [],
champion: [],
clickedChamp: false,
thisChamp: 'ahri'
}}
NewChamp = (props) =>
{
let s = props.s;
props.champion.push(s);
fetch(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`)
.then(response => { return response.json() })
.then((response) => {
Object.keys(response.data).map((a) => (s = a
))})
fetch(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`)
.then(response => { return response.json() })
.then((response) => {
console.log(s)
console.log(response.data)
console.log(props.champion)
})
console.log(`http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion/${s}.json`);
}
handleAllChamps = (props) => {
this.setState({ clickedChamp: true,
})};
handleSpecificChamp = () => {
this.setState({ clickedChamp: false,
})};
componentDidMount(props) {
const apiUrl = `http://ddragon.leagueoflegends.com/cdn/10.16.1/data/en_US/champion.json`;
fetch(apiUrl)
.then(response => { return response.json() })
.then((response) => {
this.setState({
champions: response.data
}, () => (this.state.champions))
return
})
}
render() {
const clickedChamp = this.state.clickedChamp;
let display;
if (clickedChamp ) {
display = <SpecificChamp champion={this.state.champion} onClick={this.handleSpecificChamp} s={this.state.thisChamp}/>;
} else {
display = <AllChamps champions={this.state.champions} onClick={this.handleAllChamps} NewChamp={this.NewChamp} thisChamp={this.state.thisChamp} champion={this.state.champion} />;
}
return (
<div>
<div className='champions'></div>
{display}
</div>
);
}
}
export default Champions;
The render function in class component does not has any props. You should use props from this like what you have done with handle click.
class SpecificChamp extends Component {
render() {
return (
<div className='champions'>
<h1> 1 champions</h1>
<div className='champs'>
<button onClick={this.props.onClick}></button>
{this.props.s}
</div>
</div>
)}
}

Why can't I receive ref in componentDidMount?(React)

I'm using react 16.13.1 and react-dom 16.13.1. I create a ref using React.createRef() and attach to a component I defined by myself.And then I want to use a method that I defined in that component, but it does not work because .current is null.Here's my code.
class SomeComponent {
//ref
picturesRef = React.createRef();
richTextRef = React.createRef();
componentDidMount() {
console.log("this.picturesRef", this.picturesRef);
this.setState({ operation: "update" });
const product = this.props.products.find(
(item) => item._id === this.props.match.params.id,
);
const {
name,
price,
categoryId,
imgs,
desc,
detail,
} = product;
this.setState({
name,
price,
categoryId,
imgs,
desc,
detail,
});
this.picturesRef.current.setFileList(imgs);
}
render() {
const {
categories,
isLoading,
name,
price,
categoryId,
desc,
detail,
} = this.state;
return (
<Card title={<div>Add Product</div>} loading={isLoading}>
<Form
{...layout}
onFinish={this.onFinish}
onFinishFailed={this.onFinishFailed}
initialValues={{
name,
price,
categoryId,
desc,
detail,
}}
>
<Item label="Product Pictures" name="imgs">
{/**Here I attach picturesRef to this component */}
<PicturesWall ref={this.picturesRef} />
</Item>
<Item {...tailLayout}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Item>
</Form>
</Card>
);
}
}
(P.S. When I use this.picturesRef.current in onFinish(), it works fine.)
Below is the code in PicturesWall
import React, { Component } from "react";
import { Upload, Modal, message } from "antd";
import { PlusOutlined } from "#ant-design/icons";
import { BASE_URL } from "../../config";
import { reqPictureDelete } from "../../api";
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
}
class PicturesWall extends Component {
state = {
previewVisible: false,
previewImage: "",
previewTitle: "",
fileList: [],
};
handleCancel = () => this.setState({ previewVisible: false });
handlePreview = async (file) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
this.setState({
previewImage: file.url || file.preview,
previewVisible: true,
previewTitle:
file.name ||
file.url.substring(file.url.lastIndexOf("/") + 1),
});
};
handleChange = ({ file, fileList }) => {
console.log("file=", file);
const { response, status } = file;
if (status === "done") {
if (response.status === 0) {
fileList[fileList.length - 1].url = response.data.url;
fileList[fileList.length - 1].name = response.data.name;
} else {
message.error(response.msg, 1);
}
}
if (status === "removed") {
this.deleteImg(file.name);
}
this.setState({ fileList });
};
deleteImg = async (name) => {
const response = await reqPictureDelete(name);
if (response.status === 0) {
message.success("Successfully Delete", 1);
} else {
message.error("Failed", 1);
}
};
getImgNames() {
let imgs = [];
this.state.fileList.forEach((item) => {
imgs.push(item.name);
});
return imgs;
}
setFileList = (imgNames) => {
let fileList = [];
imgNames.forEach((item, index) => {
fileList.push({
uid: index,
name: item,
url: `${BASE_URL}/upload/${item}`,
});
});
this.setState(fileList);
};
render() {
const {
previewVisible,
previewImage,
fileList,
previewTitle,
} = this.state;
const uploadButton = (
<div>
<PlusOutlined />
<div className="ant-upload-text">Upload</div>
</div>
);
return (
<div className="clearfix">
<Upload
action={`${BASE_URL}/manage/img/upload`}
method="post"
listType="picture-card"
fileList={fileList}
onPreview={this.handlePreview}
onChange={this.handleChange}
name="image"
>
{fileList.length >= 4 ? null : uploadButton}
</Upload>
<Modal
visible={previewVisible}
title={previewTitle}
footer={null}
onCancel={this.handleCancel}
>
<img
alt="example"
style={{ width: "100%" }}
src={previewImage}
/>
</Modal>
</div>
);
}
}
export default PicturesWall;
In the first line of componentDidMount, I print out this.picturesRef, and something weird happens:
in the first line, it shows that current is null, but when I open it, it seems that it has content. However, when I print .current, it is still null.
As I indicated in the comments section of the OP's question, I noticed that the Card component has a prop loading
<Card title={<div>Add Product</div>} loading={isLoading}>
<Form>
<Item>
<PicturesWall ref={this.picturesRef} />
...
This led me to believe that the Card component has conditions which prevented its children from rendering until it is finished loading, an example of this is instead of rendering its children while it's loading - it renders a "is-loading" type of component.
In this scenario, this.picturesRef.current will will return null on the componentDidMount lifecycle because the ref will not be referring to anything because it is not yet in the DOM by that time.
My original comment:
This post might shed some light. You have props such as loading on
Card which makes me think that perhaps you are initially rendering
some "is-loading" type of component on the DOM rather than the
children of Card such as the PicturesWall component. This could be why
PicturesWall ref is not accessible on the componentDidMount lifecycle
This doesn't directly answer your question, but I think you may be Reacting it wrong.
Your componentDidMount function seems to be basically only deriving state from props (and then calling a function on the reffed component). You can derive the state in a class component's constructor, e.g. something like
constructor(props) {
const product = props.products.find((item)=>item._id === props.match.params.id);
const {name,price,categoryId,imgs,desc,detail} = product;
this.state = {name,price,categoryId,imgs,desc,detail};
}
Then, instead of having a setFileList function, you would similarly pass the fileList down to PictureWall as a prop, e.g.
<PicturesWall fileList={this.state.imgs} />

How to set the background image for a div in React?

I'm creating a react application, and I have a component that is define more or less like this:
import React, { Component } from 'react';
import axios from 'axios';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
loading: true,
error: null
};
}
componentDidMount() {
var _this = this;
this.serverRequest =
axios
.get("LinkToAPI")
.then(result => {
_this.setState({
data: result.data,
loading: false,
error: null
});
})
.catch(err => {
_this.setState({
loading: false,
error: err
});
});
}
componentWillUnmount() {
this.serverRequest.abort();
}
renderLoading() {
return <div>Loading...</div>
}
renderError() {
return (
<div>
Something when wrong: {this.state.error.message}
</div>
);
}
renderData() {
const { error, data} = this.state;
if (error) {
return this.renderError();
}
return (
<div>
{data.map(d=> {
if (d.imageUrl) {
<div className="dataDiv" style="background: url('{d.imageUrl}')" key={d.Id}>{d.name}</div>
} else {
<div className="dataDiv" style="background: url('LinkToSomeImage')" key={d.Id}>{d.name}</div>
}
}
)}
</div>
)
}
render() {
return (
<div className="App">
{this.props.loading ? this.renderLoading() : this.renderData()}
</div>
);
}
}
It basically gets the JSON data from the API, and using it renders some divs with the data inside the JSON. I'm applying to the divs containing the data dataDiv class, which is define inside my App.css file. Additionally, I want to set a background image for the div. What exactly I want to do is that if the data entry includes a field named imageUrl I want to use that url as a url to the background image, otherwise, if it is null or empty, I want to use a default url that I found from the internet. What is a proper way to handle this in React? The code segment above doesn't seem to work, especially the if-else statement inside the renderData function. How can I fix this code, or is there any way to handle this more gracefully, probably maybe inside the CSS?
I would do like this
Please make sure to check backgroundUrl equal to your desired CSS.
{data.map(d => {
let backgroundUrl = "LinkToSomeImage";
if (d.imageUrl) {
backgroundUrl = d.imageUrl;
}
return (
<div className="dataDiv" style={{backgroundImage: `url(${backgroundUrl})`}} key={d.Id}>{d.name}</div>
)
})}
EDIT
A full function would be:
renderData() {
const { error, data} = this.state;
if (error) {
return this.renderError();
}
return (
<div>
{data.map(d => {
let backgroundUrl = "LinkToSomeImage";
if (d.imageUrl) {
backgroundUrl = d.imageUrl;
}
return (
<div className="dataDiv" style={{backgroundImage: `url(${backgroundUrl})`}} key={d.Id}>{d.name}</div>
)
})}
</div>
)
}
<Box
onClick={() => {
history.push({
pathname: `/p/c/${data.ProductName.replace(/\//g, "~")}/1`
});
}}
css={{
backgroundImage:`url(${data.imageUrl||"/default-placeholder.png"})`,
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat'
}}
>

Categories

Resources