My nextjs app uses getInitialProps to fetch data from a streaming API, and then checks every second thereafter for updates. When the API changes (when the current track ends and a new one begins) I would like for the trackName to change on screen. What I have here works so long as I log the output to console, but for some reason the UI will not update.
I believe that trackName is passed down to the child as a prop? And then the child is trying to set the state in componentDidMount? My understanding of this feels shaky.
This is my _app.js:
import React, { useState } from 'react'
import Player from '../components/Player'
function MyApp({ Component, pageProps, tracks }) {
const url = "https://kchungradio.out.airtime.pro/kchungradio_a"
const trackName = tracks.current.name
return (
<>
<Player
url={url}
trackName={trackName}
/>
<Component {...pageProps} />
</>
)
}
MyApp.getInitialProps = async (appContext) => {
let res = await fetch("https://kchungradio.airtime.pro/api/live-info-v2")
let data = await res.json()
return { tracks: data.tracks }
}
export default MyApp
And this is my Player:
import { Component } from 'react'
import React from 'react'
import ReactPlayer from 'react-player'
export default class Player extends React.Component {
constructor(props) {
super(props)
}
componentDidMount() {
setInterval(() => {
fetch("https://kchungradio.airtime.pro/api/live-info-v2", {
method: "GET"
})
.then(response => {
if (!response.ok) {
return Promise.reject(response)
}
return response.json()
})
.then(data => {
this.updateCurrentTrack(data.tracks.current.name)
})
.catch(error => {
if (typeof error.json === "function") {
error.json().then(jsonError => {
console.log(jsonError)
}).catch(genericError => {
console.log("Generic error from API")
console.log(error.statusText)
})
} else {
console.log("Fetch error")
console.log(error)
}
})
}, 1000)
}
updateCurrentTrack = (name) => {
this.setState({
trackName: name
})
console.log(name)
}
render() {
const { url, trackName } = this.props
return (
<div class="player">
<ReactPlayer
url={url}
/>
<div>{trackName}</div>
</div>
)
}
}
this.props.trackName is not the same than this.state.trackName. You are setting a state element that is never declared in the contructor and using the trackName froms this.props which is only received once from it's parent component.
try this in the constructor:
constructor(props) {
super(props)
this.state = {
trackName: '',
}
}
and in your jsx
render() {
const { url, trackName } = this.props
const { trackName: stateTrackName } = this.state // rename since trackName has already been declared
return (
<div class="player">
<ReactPlayer
url={url}
/>
<div>{stateTrackName || trackName}</div>
// or whats the same
// <div>{this.state.trackName || this.props.trackName}</div>
</div>
)
}
My idea is that please use useState and useEffect.
In my next.js applications:
I import useEffect and useState from react.
Create 2 constact in useState.
Create a useEffect that and in the array of useEffect, set first const.
In the useEffect, change amount of second const.
This code is a dashboard page of one of my apps.
// IMPORT CTRL
import DashboardCtrl from "../../components/dashboard/ctrl";
// IMPORT DETAILS COMPONENTS
import UserInfo from "../../components/dashboard/userInfo";
import NewProjects from "../../components/dashboard/newProjects";
import { useState,useEffect } from "react";
const Dashboard = () => {
const [content,setcontent]=useState("UserInfo");
const [details,setdetails]=useState(<UserInfo/>);
useEffect(()=>{
if (content=="UserInfo") {
setdetails(<UserInfo/>);
}else if(content=="NewProjects"){
setdetails(<NewProjects/>);
}
},[content])
return (
<main>
<div className=" container mx-auto flex justify-between items-start my-8">
<div className=" w-1/4">
{details}
</div>
<div className=" w-1/4">
<DashboardCtrl setcontent={setcontent}/>
</div>
</div>
</main>
);
}
export default Dashboard;
Related
I am fairly new to testing React applications with Jest and Enzyme. I am trying to test some connected components, but don't understand how to mock data for those. I have an API call and use props. Here are my files:
News Page
import React, { Component } from 'react';
import './news.style.css';
import NewsList from '../../components/news-list/news-list.component';
import { SearchBar } from '../../components/search-bar/search-bar.component';
import Header from '../../components/header/header.component';
import { NewsFooter } from '../../components/news-footer/news-footer.component';
class News extends Component {
constructor() {
super();
this.state = {
news: [],
searchField: '',
topics: ''
};
}
componentDidMount() {
fetch('https://sheltered-earth-94987.herokuapp.com/news')
.then(response => response.json())
.then(news => this.setState({ news: news}));
}
render() {
const { news, searchField, topics } = this.state;
const filteredNewsname = news
.filter(news => news.news_topic.toLowerCase().includes(topics.toLowerCase()))
.filter(news => news.news_headline.toLowerCase().includes(searchField.toLowerCase()));
return (
<div>
<Header/>
<h1 className="pageheadline">News</h1>
<SearchBar
placeholder='Search News'
handleChange= {e => this.setState({ searchField: e.target.value})}
/>
<div className="newslist">
<NewsList news={filteredNewsname}>
</NewsList>
</div>
<div className="newsfooter">
<NewsFooter
handleClick= {e => this.setState({ topics: e.target.id})}
/>
</div>
</div>
);
}
}
export default News;
NewsList Component
import React from 'react';
import './news-list.style.css';
import { NewsCard } from '../news-card/news-card.component';
import { Link } from 'react-router-dom';
const NewsList = props => {
return <div className='news-list'>
{
props.news.map(newsentry => <Link to={`/news/${newsentry.news_id}`}>
<NewsCard key={newsentry.news_id} newsentry={newsentry}/></Link>)
}
</div>;
};
export default NewsList;
NewsCard Component
import React from 'react';
import './news-card.style.css';
const NewsCard = props => (
<div className='news-card-container' data-test="news-card-container">
<img className="newsimg" alt="Newsimage" src={ props.newsentry.news_header_image}></img>
<div className="newsinfo">
<h4 className="newstitle"> { props.newsentry.news_headline } </h4>
<p className="teaser">{props.newsentry.news_teaser}</p>
<p className="author">By {props.newsentry.news_author} </p>
</div>
<p className="newstopic">#{props.newsentry.news_topic}</p>
</div>
)
export default NewsCard;
How can I test the NewsList and the NewsCard Components with mocked data?
This is how I started:
Testfile
import { shallow } from 'enzyme';
import React from 'react';
import NewsCard from './news-card.component';
import { findByTestAttr } from '../../../utils/index';
const setUp = (props={}) => {
const component = shallow(<NewsCard {... props}/>);
return component;
}
describe('NewsCard Component', () => {
describe('Have props', () => {
let wrapper;
beforeEach(() => {
const props = {
news: [],
};
wrapper = setUp(props);
});
it('Should render without errors', async () => {
const component = findByTestAttr(wrapper, 'news-card-container');
expect(component.length).toBe(1);
})
});
})
File with findByTestAttr function
export const findByTestAttr = (component, attr) => {
const wrapper = component.find(`[data-test='${attr}']`);
return wrapper;
}
For this right now I get an error, saying:
TypeError: Cannot read properties of undefined (reading 'news_header_image')
Before rendering components in test block; you can provide a new, mockup variable to your component.
Example:
<NewsCard key={..} newsentry={mockupNewsentry}/> with mockupNewsentry being your mockup data variable.
Longer example:
test("renders singleitem with data", async () => {
const mockupData = {
name: "Homer Simpson",
job: "Nuclear Safety Inspector",
id: "14",
};
render(
<SingleItem data={mockupData} />
);
const element = await screen.findByText(/Homer Simpson/i);
expect(element).toBeInTheDocument();
});
Check out this package. It will mock the network layer. Everyone is using this one for integration testing of components.
https://mswjs.io
Ok so I am trying to understand React Hooks and how to update
my code to grab the JSON from the source below and show the data. I'm clear on importing the hook and initializing it with useState(0) but my code fails when I try to re-factor within my fetch statement. Any/all help would be greatly appreciated...see below.
// import React, { Component } from 'react';
import React, { useState } from 'react';
import Feeder from './Feeder';
import Error from './Error';
// class NewsFeeder extends Component {
// constructor(props) {
// super(props);
// this.state = {
// news: [],
// error: false,
// };
// }
const [hideNews,showNews] = useState(0);
componentDidMount() {
const url = `https://newsfeed.com`;
fetch(url)
.then((response) => {
return response.json();
})
.then((data) => {
this.setState({
news: data.articles
})
})
.catch((error) => {
this.setState({
error: true
})
});
}
renderItems() {
if (!this.state.error) {
return this.state.news.map((item) => (
<FeedPrime key={item.url} item={item} />
));
} else {
return <Error />
}
}
render() {
return (
<div className="row">
{this.renderItems()}
</div>
);
}
}
export default NewsFeeder;
React hooks are created for functional components and are not ment to be used in class components.
Here is a table of the functionality and the way to achive it using classes and functions with hooks.
component type
state
fetch
class
store the state in this.state that you only assign once in the constructor, use this.setState to modify the state
do your fetch logic in componentDidMount
function
create a pair of [example, setExample] with useState
do fetch in useEffect hook
Using fetch with hooks: (edited version of this):
import React, { useState, useEffect } from 'react';
function App() {
const [data, setData] = useState({ hits: [] });
useEffect(async () => {
const result = await fetch('https://hn.algolia.com/api/v1/search?query=redux').then(response => response.json());
setData(result);
});
let items = data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
));
return (
<ul>
{items}
</ul>
);
}
export default App;
I am trying to use promise tracker. I used react-promise-tracker package.
Below the code of my component:
import React from "react";
import { usePromiseTracker } from "react-promise-tracker";
import Spinner from "./Spinner";
const LdngIndicator = (props) => {
const { promiseInProgress } = usePromiseTracker();
return promiseInProgress && <Spinner />;
};
export default function LoadingIndicator() {
return <LdngIndicator />;
}
parent.js
import React, { Component } from "react";
import { trackPromise } from "react-promise-tracker";
import LoadingIndicator from "./LoadingIndicator";
export default class App extends Component {
constructor() {
super();
this.state = {
randomResponse: "",
};
}
apifunc = () => {
trackPromise(
fetch(
"https://****.rapidapi.com/random",
{
method: "GET",
headers: {
accept: "application/json",
"x-rapidapi-key": process.env.REACT_APP_KEY,
"x-rapidapi-host": "host.rapidapi.com",
},
}
)
.then((res) => res.json())
.then((res) => {
this.setState({
randomResponse: res,
});
})
.catch((err) => {
console.error(err);
})
);
};
handleSubmit = (e) => {
e.preventDefault();
this.apifunc();
};
render() {
return (
<div className="wrapper">
<form className="jokesForm" onSubmit={this.handleSubmit}>
<label>Get a new joke</label>
<input type="submit" value="Go!"></input>
</form>
<main>
{<LoadingIndicator /> ? <p>True</p> : <p>false</p>}
</main>
</div>
);
}
}
I can run this without any issues. I would like to get boolean value of the promiseInProgress in my parent component.
promiseInProgress changes as I make requests.
What is the best way to send this as props?
I read https://reactjs.org/docs/components-and-props.html as well as different questions on stack overflow but I was not able to find an answer to my quesiton.
I appreciate any help.
you didn't show us your parent component though so let's assume its a functional component
//parent.jsx
import react, { useState } from 'react'
import Loader from 'loader.jsx'
const ParentComp = () => {
const [state, setState] = useState(false)
return <>
<Loader setState={setState} setState={setState} />
<span> {`state: ${state}`} <span/>
</>
}
export default ParentComp
//loader.jsx
import React from "react";
import { usePromiseTracker } from "react-promise-tracker";
import Spinner from "./Spinner";
const LdngIndicator = () => {
const { promiseInProgress } = usePromiseTracker();
return promiseInProgress && <Spinner />;
};
export default function LoadingIndicator({state, setState}) {
return <>
<button onClick={() => setState(!state)}>click</button>
<LdngIndicator />
</>
}
you get the idea? what you can easily do is to pass setState to child component and modify it from the child
Just having troubles, sorry for the noob question, but i can't seem to log the props results in the DisplayData.js file. I am trying to pass the SearchStockResult state to the DisplatData.js file... I have tried to console log the data property with no luck. Not to sure what is going on. Excuse the naming conventions as I had just changed it from a stock search to a crypto search.
import React, { Component } from 'react'
import DisplayData from './DisplayData';
export default class stockSearch extends Component {
state = {
searchResult: '',
}
componentDidMount = () => {
fetch(`https://min-api.cryptocompare.com/data/pricemulti?fsyms=BTC,ETH,IOT&tsyms=USD`)
.then((response) => response.json())
.then(data => {
this.setState({ searchResult: data });
console.log(this.state.searchResult);
});
}
render() {
const {data} = this.state;
return (
<form>
<label>
Name:
<input type="text" name="query" />
</label>
<button>Search Crypto</button>
<DisplayData results={data} />
</form>
)
}
}
import React, { Component } from 'react'
export default class DisplayData extends Component {
dataResults = (props) => {
console.log('from data results', props.results);
}
render() {
return (
<div>
</div>
)
}
}
Make few corrections:
State update is async. So pass a call back function if you want to print it
in StockSearch component, you need to destructure searchResult from state (not data)
In DisplayData component use this.props. Also call your function(dataResults) in render method so that it is called and rendered
Working copy of your code is here
StockSearch Component
import React, { Component } from "react";
import DisplayData from "./DisplayData";
export default class StockSearch extends Component {
state = {
searchResult: ""
};
componentDidMount = () => {
fetch(
`https://min-api.cryptocompare.com/data/pricemulti?fsyms=BTC,ETH,IOT&tsyms=USD`
)
.then(response => response.json())
.then(data => {
this.setState({ searchResult: data }, () => {
console.log("api result", this.state.searchResult);
});
});
};
render() {
const { searchResult } = this.state;
return (
<form>
<label>
Name:
<input type="text" name="query" />
</label>
<button>Search Crypto</button>
<DisplayData results={searchResult} />
</form>
);
}
}
DisplayData Component
import React, { Component } from "react";
export default class DisplayData extends Component {
dataResults = () => {
console.log("from data results", this.props.results);
};
render() {
this.dataResults() // just to print
return <div>{return <div>{JSON.stringify(this.props.results)}</div>;}</div>;
}
}
I get the pathname in the WorldPage component and pass this value to the context.jsx in which I want to request data using the pathname.
However, I cannot get the correct value in the componentDidMount() method.
console.log(this.state.tab) should be /world, but still /home.
import axios from "axios";
export const Context = React.createContext();
export class Provider extends Component {
state = {
news_list: [],
tab: "/home",
tabChange: (tabName) => {
if (this.state.tab !== tabName) {
this.setState({
tab: tabName,
});
}
},
};
componentDidMount() {
console.log(this.state.tab);
axios
.get(this.state.tab)
.then((res) => {
console.log(res.data);
this.setState({
news_list: res.data,
});
// console.log(this.state.news_list);
})
.catch((err) => console.log(err));
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
export const Consumer = Context.Consumer;
import React, { Component } from "react";
import News from "../News/News";
import { Consumer } from "../../context";
export default class WorldPage extends Component {
render() {
const tabName = window.location.pathname;
return (
<Consumer>
{(value) => {
const { tabChange } = value;
tabChange(tabName);
console.log(tabName);
return (
<React.Fragment>
<News />
</React.Fragment>
);
}}
</Consumer>
);
}
}