ReactJS - Loading undesirable repeated data from the API - reactjs

I'm fetching Data from a Dribbble API. It is almost succefully but it is loading 12 shots from the API, and after this it is occurring a problem by repeating the same 12 again and then it after this it is loading the others in a right way: 13,14,15...
The shots' return:
1,2,3,4,5,6,7,8,9,10,11,12.
OK RIGHT
1,2,3,4,5,6,7,8,9,10,11,12.
repetition problem!!! WRONG
13,14,15,16... OK RIGHT
What is the solution for this undesirable repetition?
Here is the code:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Waypoint from 'react-waypoint';
import Dribbble from './Dribbble';
export default class Dribbbles extends Component {
constructor(props) {
super(props);
this.state = { page: 1, shots: [] };
this.getShots = this.getShots.bind(this);
}
componentDidMount() {
this.getShots();
}
getShots() {
return $.getJSON('https://api.dribbble.com/v1/shots?page='+ this.state.page + '&access_token=ACCESS_TOKEN_HERE&callback=?')
.then((resp) => {
var newShots = this.state.shots.concat(resp.data);
this.setState({
page: this.state.page + 1,
shots: newShots
});
});
}
render() {
const shots = this.state.shots.map((val, i) => {
return <Dribbble dados={val} key={i} />
});
return (
<div>
<ul>{shots}</ul>
<Waypoint
onEnter={this.getShots}
/>
</div>
);
}
}

Why
I think the onEnter event from your Waypoint component is triggered unexpectedly, which has caused the getShots() function being called multiple times.
How
My suggestion is to specify a dataFecthed state to control whether the Waypoint component mount or not.Just like below:
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import Waypoint from 'react-waypoint'
import Dribbble from './Dribbble'
export default class Dribbbles extends Component {
constructor(props) {
super(props)
this.state = { page: 1, shots: [], dataFetched: true }
this.getShots = this.getShots.bind(this)
}
componentDidMount() {
this.getShots()
}
getShots() {
this.setState({
dataFetched: false,
})
return $.getJSON(
'https://api.dribbble.com/v1/shots?page=' +
this.state.page +
'&access_token=41ff524ebca5e8d0bf5d6f9f2c611c1b0d224a1975ce37579326872c1e7900b4&callback=?'
).then(resp => {
var newShots = this.state.shots.concat(resp.data)
this.setState({
page: this.state.page + 1,
shots: newShots,
dataFetched: true,
})
})
}
render() {
const shots = this.state.shots.map((val, i) => {
return <Dribbble dados={val} key={i} />
})
return (
<div>
<ul>{shots}</ul>
{this.state.dataFetched && <Waypoint onEnter={this.getShots} />}
</div>
)
}
}

Related

Child component not triggering rendering when parent injects props with differnt values

Here are my components:
App component:
import logo from './logo.svg';
import {Component} from 'react';
import './App.css';
import {MonsterCardList} from './components/monster-list/monster-card-list.component'
import {Search} from './components/search/search.component'
class App extends Component
{
constructor()
{
super();
this.state = {searchText:""}
}
render()
{
console.log("repainting App component");
return (
<div className="App">
<main>
<h1 className="app-title">Monster List</h1>
<Search callback={this._searchChanged}></Search>
<MonsterCardList filter={this.state.searchText}></MonsterCardList>
</main>
</div>
);
}
_searchChanged(newText)
{
console.log("Setting state. new text: "+newText);
this.setState({searchText:newText}, () => console.log(this.state));
}
}
export default App;
Card List component:
export class MonsterCardList extends Component
{
constructor(props)
{
super(props);
this.state = {data:[]};
}
componentDidMount()
{
console.log("Component mounted");
this._loadData();
}
_loadData(monsterCardCount)
{
fetch("https://jsonplaceholder.typicode.com/users", {
method: 'GET',
}).then( response =>{
if(response.ok)
{
console.log(response.status);
response.json().then(data => {
let convertedData = data.map( ( el, index) => {
return {url:`https://robohash.org/${index}.png?size=100x100`, name:el.name, email:el.email}
});
console.log(convertedData);
this.setState({data:convertedData});
});
}
else
console.log("Error: "+response.status+" -> "+response.statusText);
/*let data = response.json().value;
*/
}).catch(e => {
console.log("Error: "+e);
});
}
render()
{
console.log("filter:" + this.props.filter);
return (
<div className="monster-card-list">
{this.state.data.map((element,index) => {
if(!this.props.filter || element.email.includes(this.props.filter))
return <MonsterCard cardData={element} key={index}></MonsterCard>;
})}
</div>
);
}
}
Card component:
import {Component} from "react"
import './monster-card.component.css'
export class MonsterCard extends Component
{
constructor(props)
{
super(props);
}
render()
{
return (
<div className="monster-card">
<img className="monster-card-img" src={this.props.cardData.url}></img>
<h3 className="monster-card-name">{this.props.cardData.name}</h3>
<h3 className="monster-card-email">{this.props.cardData.email}</h3>
</div>
);
}
}
Search component:
import {Component} from "react"
export class Search extends Component
{
_searchChangedCallback = null;
constructor(props)
{
super();
this._searchChangedCallback = props.callback;
}
render()
{
return (
<input type="search" onChange={e=>this._searchChangedCallback(e.target.value)} placeholder="Search monsters"></input>
);
}
}
The problem is that I see how the text typed in the input flows to the App component correctly and the callback is called but, when the state is changed in the _searchChanged, the MonsterCardList seems not to re-render.
I saw you are using state filter in MonsterCardList component: filter:this.props.searchText.But you only pass a prop filter (filter={this.state.searchText}) in this component. So props searchTextis undefined.
I saw you don't need to use state filter. Replace this.state.filter by this.props.filter
_loadData will get called only once when the component is mounted for the first time in below code,
componentDidMount()
{
console.log("Component mounted");
this._loadData();
}
when you set state inside the constructor means it also sets this.state.filter for once. And state does not change when searchText props change and due to that no rerendering.
constructor(props)
{
super(props);
this.state = {data:[], filter:this.props.searchText};
}
If you need to rerender when props changes, use componentDidUpdate lifecycle hook
componentDidUpdate(prevProps)
{
if (this.props.searchText !== prevProps.searchText)
{
this._loadData();
}
}
Well, in the end I found what was happening. It wasn't a react related problem but a javascript one and it was related to this not been bound to App class inside the _searchChanged function.
I we bind it like this in the constructor:
this._searchChanged = this._searchChanged.bind(this);
or we just use and arrow function:
_searchChanged = (newText) =>
{
console.log("Setting state. new text: "+newText);
this.setState({filter:newText}, () => console.log(this.state));
}
Everything works as expected.

React, How to use a menu in a seperate file to call an api and return data to a different section of the main file

I have a react app with a large menu, and as such am trying to move it to a seperate file from the main app.js
at the mement when you click on a link in the menu it call a node api and which returns some data, however when I try to seperate I can not get it to populate the results section which is still in the main script
Working version app.js
import React,{ useState } from 'react';
import './App.css';
import axios from 'axios';
import { Navigation } from "react-minimal-side-navigation";
import "react-minimal-side-navigation/lib/ReactMinimalSideNavigation.css";
export default class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
result: [],
};
this.callmyapi = this.callmyapi.bind(this);
}
render() {
return (
<div>
<div class="menu">
<Navigation
onSelect={({itemId}) => {
axios.get(`/api/menu/`, {
params: {
Menu: itemId,
}
})
.then(res => {
const results = res.data;
this.setState({ results });
})
.catch((err) => {
console.log(err);
})
}}
items={[
{
title: 'Pizza',
itemId: '/menu/Pizza/',
},
{
title: 'Cheese',
itemId: '/menu/cheese',
}
]}
/>
</div>
<div class="body">
this.state.results && this.state.results.map(results => <li>* {results.Name}</li>);
</div>
</div>
);
}
}
New app.js
import React,{ useState } from 'react';
import './App.css';
//import axios from 'axios';
//import { Navigation } from "react-minimal-side-navigation";
//import "react-minimal-side-navigation/lib/ReactMinimalSideNavigation.css";
import MyMenu from './mymenu';
export default class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
result: [],
};
this.callmyapi = this.callmyapi.bind(this);
}
render() {
return (
<div>
<div class="menu">
<MyMenu />
</div>
<div class="body">
this.state.results && this.state.results.map(results => <li>* {results.Name}</li>);
</div>
</div>
);
}
}
New menu file
mymenu.js
import React, { Component } from 'react';
import axios from 'axios';
import './App.css';
//import MyList from './App.js';
//import { ProSidebar, Menu, MenuItem, SubMenu } from 'react-pro-sidebar';
//import 'react-pro-sidebar/dist/css/styles.css';
import { Navigation } from "react-minimal-side-navigation";
//import Icon from "awesome-react-icons";
import "react-minimal-side-navigation/lib/ReactMinimalSideNavigation.css";
//export default async function MyMenu(){
export default class MyMenu extends React.Component {
constructor(props) {
super(props);
};
render() {
return (
<div>
<Navigation
// you can use your own router's api to get pathname
activeItemId="/management/members"
onSelect={({itemId}) => {
// return axios
axios.get(`/api/menu/`, {
params: {
// Menu: itemId,
Menu: "meat",
SubMenu : "burgers"
}
})
.then(res => {
const results = res.data;
this.setState({ results });
})
.catch((err) => {
console.log(err);
})
}}
items={[
{
title: 'Pizza',
itemId: '/menu/Pizza/',
},
{
title: 'Cheese',
itemId: '/menu/cheese',
}
]}
/>
</div>
);
}
}
Any help would be greatly appreciated
That one is quite easy once you understand state. State is component specific it that case. this.state refers to you App-Component and your Menu-Component individually. So in order for them to share one state you have to pass it down the component tree like this.
export default class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
result: [],
};
}
render() {
return (
<div>
<div class="menu">
<MyMenu handleStateChange={(results: any[]) => this.setState(results)} />
</div>
<div class="body">
this.state.results && this.state.results.map(results => <li>* {results.Name}</li>);
</div>
</div>
);
}
}
See this line: <MyMenu handleStateChange={(results: any[]) => this.setState(results)} />
There you pass a function to mutate the state of App-Component down to a the child
There you can call:
onSelect={({itemId}) => {
// return axios
axios.get(`/api/menu/`, {
params: {
// Menu: itemId,
Menu: "meat",
SubMenu : "burgers"
}
})
.then(res => {
const results = res.data;
this.props.handleStateChange(results)
})
.catch((err) => {
console.log(err);
})
You mutate the parent state and the correct data is being rendered. Make sure to practice state and how it works and how usefull patterns look like to share state between components.
Thanks - I Have found solution (also deleted link question)
above render added function
handleCallback = (results) =>{
this.setState({data: results})
}
then where I display the menu
<MyMenu parentCallback = {this.handleCallback}/>
where i display the results
{this.state.results && this.state.results.map(results => <li>{results.Name}</li>}
No aditional changes to the menu scripts

Show a Simple Animation on setState in react

I display a data from JSON file to the DIV in my component.
I Set timeout for few seconds and after that the data displays.
I want to show a simple animation when the state changes to true.
Here is a sample of my Code Structure:
import someData from "../Data";
export default class Example extends Component {
constructor(props) {
super(props);
this.state = { contentLoad: false }
}
componentDidMount() {
setTimeout(() => {
this.setState({
contentLoad: true
})
}, 2500)
}
render() {
return (
<div>
{someData.map((someData) => {
return (
<div> {this.state.contentLoad && someData.name}</div>
)
})}
</div>
)
}
}
I read about react transition group, but cant understand cause i'm new to react. someone please use this as a template and provide me a codepen or codesandbox link for solution.
I agree with #JohnRuddell that Pose is a heavy library if all you need is a simple fade in. However I would look it if you are doing multiple animations that are more complex.
Sample code:
import React from "react";
import ReactDOM from "react-dom";
import posed from "react-pose";
import "./styles.css";
const AnimatedDiv = posed.div({
hidden: { opacity: 0 },
visible: { opacity: 1 }
});
class Example2 extends React.Component {
constructor(props) {
super(props);
this.state = { contentLoad: false };
}
componentDidMount() {
setTimeout(() => {
this.setState({
contentLoad: true
});
}, 2500);
}
someData = ["hello", "hi", "test"];
render() {
return (
<AnimatedDiv pose={this.state.contentLoad ? "visible" : "hidden"}>
{this.someData.map(t => {
return <div>{t}</div>;
})}
</AnimatedDiv>
);
}
}
ReactDOM.render(<Example2 />, document.getElementById("root"));
Sandbox link

react change slide in slider after clicking a button

I have a Sidebar that I've implemented this way:
import React from "react";
import './MySidebar.scss';
import {slide as Menu } from 'react-burger-menu'
export class MySidebar extends React.Component {
constructor(props) {
super(props);
}
changeSlide = (e) => {
console.log("Clicked " + e.currentTarget.id); //get text content of
}
render() {
return (
<Menu customCrossIcon={false}>
<button onClick={((e) => this.changeSlide(e))} className ="menu-item" id="Project1">Project 1</button>
<button onClick={((e) => this.changeSlide(e))} className ="menu-item" id="Project2">Project 2</button>
</Menu>
);
}
}
Then I have a component called ProjectSliderComponent that realizes the behaviour of a carousel. It is done this way:
import React from "react";
import Slider from "react-slick";
import './project-carousel.component.css';
import { ProjectComponent } from '../project/project.component';
import { LoggerService } from '../../services/logger-service';
import { appConfig } from '../../config';
export class ProjectSliderComponent extends React.Component {
state = {
activeSlide: 0,
timestamp: Date.now()
}
constructor(props) {
super(props);
this.logger = new LoggerService();
this.settings = {
dots: appConfig.dots,
arrows: false,
adaptiveHeight: true,
infinite: appConfig.infinite,
speed: appConfig.speed,
autoplay: appConfig.autoplay,
autoplaySpeed: appConfig.autoplaySpeed,
slidesToShow: 1,
slidesToScroll: 1,
mobileFirst: true,
className: 'heliosSlide',
beforeChange: (current, next) => {
this.setState({ activeSlide: next, timestamp: Date.now() });
}
};
}
render() {
let i = 0;
return (
<div>
<Slider ref = {c => (this.slider = c)} {...this.settings}>
{
this.props.projectListId.data.map(projectId =>
<ProjectComponent key={projectId} id={projectId} time={this.state.timestamp} originalIndex={ i++ } currentIndex = {this.state.activeSlide}></ProjectComponent>)
}
</Slider>
</div>
);
}
}
The ProjectComponent component simply specifies the layout. This is the App.js file, where I load the projectslidercomponent and my sidebar. I do this:
class App extends Component {
state = {
projectList: new ProjectListModel(),
isLoading: true
}
constructor(props) {
super(props);
this.logger = new LoggerService();
}
componentDidMount() {
let projectService = new ProjectService();
projectService.getProjectList()
.then(res => {
let projectList = new ProjectListModel();
projectList.data = res.data.data;
this.setState({ projectList: projectList,
isLoading: false });
})
.catch(error => {
this.logger.error(error);
});
}
render() {
return (
<div className="App">
<MySidebar>
</MySidebar>
<ProjectSliderComponent projectListId={this.state.projectList}></ProjectSliderComponent>
</div>
);
}
}
export default App;
What I need to do is to change the slide according to which button I clicked. What do I have to do? Is there something like passing the "instance" of the projectslidercomponent and call a method to change the slide? Or something else?
At the react-slick docs you can read about slickGoTo, which is a method of the Slider component.
Since you're already storing this.slider you can try to make it accessible in MySidebar and use it whenever changeSlide is called. Most likely you have to create a changeSlide method on top level and hand it as property to your components.
To sort this problem what you have to do is create a function in parent component which updates the state of the app component and once the state is updated it will re render app component and the new props are send to slider component which will tell which slider to show. Below is the code :-
In App.js
class App extends Component {
state = {
projectList: new ProjectListModel(),
isLoading: true,
menuId: 0
}
constructor(props) {
super(props);
this.logger = new LoggerService();
}
componentDidMount() {
let projectService = new ProjectService();
projectService.getProjectList()
.then(res => {
let projectList = new ProjectListModel();
projectList.data = res.data.data;
this.setState({ projectList: projectList,
isLoading: false });
})
.catch(error => {
this.logger.error(error);
});
}
ChangeSlide = (menuId) => {
this.setState({
menuId //Here you will receive which slide to show and according to that render slides in mySliderbar component
})
}
render() {
return (
<div className="App">
<MySidebar ChangeSlide={this.ChangeSlide} />
<ProjectSliderComponent menuId={this.state.menuId} projectListId={this.state.projectList}></ProjectSliderComponent>
</div>
);
}
}
export default App;
In mySlidebar
import React from "react";
import './MySidebar.scss';
import {slide as Menu } from 'react-burger-menu'
export class MySidebar extends React.Component {
constructor(props) {
super(props);
}
changeSlide = (e) => {
console.log("Clicked " + e.currentTarget.id); //get text content of
this.props.changeSlide(e.currentTarget.id)
}
render() {
return (
<Menu customCrossIcon={false}>
<button onClick={((e) => this.changeSlide(e))} className ="menu-item" id="Project1">Project 1</button>
<button onClick={((e) => this.changeSlide(e))} className ="menu-item" id="Project2">Project 2</button>
</Menu>
);
}
}
In slider component, you have to listen when the new props are coming and according to that change the slide see componentWillReceiveProps
import React from "react";
import Slider from "react-slick";
import './project-carousel.component.css';
import { ProjectComponent } from '../project/project.component';
import { LoggerService } from '../../services/logger-service';
import { appConfig } from '../../config';
export class ProjectSliderComponent extends React.Component {
state = {
activeSlide: 0,
timestamp: Date.now()
}
constructor(props) {
super(props);
this.logger = new LoggerService();
this.settings = {
dots: appConfig.dots,
arrows: false,
adaptiveHeight: true,
infinite: appConfig.infinite,
speed: appConfig.speed,
autoplay: appConfig.autoplay,
autoplaySpeed: appConfig.autoplaySpeed,
slidesToShow: 1,
slidesToScroll: 1,
mobileFirst: true,
className: 'heliosSlide',
beforeChange: (current, next) => {
this.setState({ activeSlide: next, timestamp: Date.now() });
}
};
}
componentWillReceiveProps(nextProps) {
if(nextprops.menuId != this.props.menuId){
this.slider.slickGoTo(nextprops.menuId, true)//use slider instance to
//call the function to go to a particular slide.
}
}
render() {
let i = 0;
const { menuId } = this.props
return (
<div>
<Slider initialSlide={menuId} ref = {c => (this.slider = c)} {...this.settings}>
{
this.props.projectListId.data.map(projectId =>
<ProjectComponent key={projectId} id={projectId} time={this.state.timestamp} originalIndex={ i++ } currentIndex = {this.state.activeSlide}></ProjectComponent>)
}
</Slider>
</div>
);
}
}

How to send automatic constant message in web app based on reactjs?

This is my App.js and i have set my database in firebase. All the messages which i enter all display in database also.But i need to automatically send message back to me . so any one knows how to do that please help. Thank you.
import React, { Component } from 'react';
import MessagePane from './MessagePane';
import ChannelList from './ChannelList';
import { getMessages, getChannels, saveMessage, onNewMessage } from './remote_storage1';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {
messages: [],
channels: [],
selected_channel_id: null
};
this.onSendMessage = this.onSendMessage.bind(this);
this.onChannelSelect = this.onChannelSelect.bind(this);
this.filteredMessages = this.filteredMessages.bind(this);
}
componentDidMount() {
getMessages().then(messages => this.setState({messages}));
getChannels().then(channels => this.setState({channels, selected_channel_id: channels[0].id}));
onNewMessage(new_message => {
const messages = [...this.state.messages, new_message];
this.setState({messages});
});
}
onSendMessage(author, text) {
const new_message = {
id: this.state.messages[this.state.messages.length - 1].id + 1,
author,
text,
channel_id: this.state.selected_channel_id
};
saveMessage(new_message);
const messages = [...this.state.messages, new_message];
this.setState({messages});
}
onChannelSelect(id) {
this.setState({ selected_channel_id: id });
}
filteredMessages() {
return this.state.messages.filter(({channel_id}) => channel_id === this.state.selected_channel_id);
}
render() {
return (
<div className="App">
<ChannelList
channels={this.state.channels}
selectedChannelId={this.state.selected_channel_id}
onSelect={this.onChannelSelect}
/>
<MessagePane messages={this.filteredMessages()} onSendMessage={this.onSendMessage} />
</div>
);
}
}
export default App;

Resources