Passing a state value in functional component in React - reactjs

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

Related

confused by state/props and updating UI in nextjs app

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;

React testing with Jest and Enzyme: How to write test for connected components with API call and props

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

React TypeError: this.props.message.map is not a function

I am trying to display messages on the screen which I receive from api. I checked in the debugger (here are the screenshots https://ibb.co/gShTG8g https://ibb.co/dQmfwJp) where all the stages are going fine, but in the end I get an error called TypeError: this.props.message.map is not a function. Here is my actual code, link to api https://rapidapi.com/ajith/api/messages
Messages.jsx
import React from "react";
export class Messages extends React.Component {
render() {
const MessageList = this.props.message.map((item, index) => {
return <div key={index}>
<p>{item.Message}</p>
</div>
});
return(
<div>
{MessageList}
</div>
);
}
}
MessagesContainer.js
import React from 'react';
import { connect } from 'react-redux';
import {Messages} from "./Messages";
import {getMessagesThunk} from "../../Redux/users-reducer";
class MessagesContainer extends React.Component {
componentDidMount() {
this.props.getMessagesThunk();
}
render() {
return(
<>
<Messages {...this.props} />
</>
)
}
}
let mapStateToProps = (state) => ({
message: state.usersPage.messages
})
export default connect(mapStateToProps, {getMessagesThunk})(MessagesContainer);
users-reducer.js (Here is a part of my code)
let initialState = {
messages: [],
};
case MESSAGE:
return {
...state, messages: action.messages
}
export const getMessage = (messages) => ({type: MESSAGE, messages})
export const getMessagesThunk = (messages) => {
return (dispatch) => {
usersAPI.message(messages).then(response => {
if(response.data) {
dispatch(getMessage(response.data.Message))
}
})
}
}
Api.js
import axios from "axios";
const instance = axios.create({
params: {category: 'love'},
withCredentials: true,
headers: {
"API-KEY": "6bec01a1-e00c-42ca-ab9d-a03ad2e730cc",
'x-rapidapi-key': 'bf490d72a0msh3bf159a87e0c27fp107a51jsn062ca1b9b00e',
'x-rapidapi-host': 'ajith-messages.p.rapidapi.com'
}
})
export const usersAPI = {
message() {
return instance.get(`https://ajith-messages.p.rapidapi.com/getMsgs`)
},
}
This is because the message is not an array you can loop through. It's a simple string.

Pass props to different components in react

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>;
}
}

Pass value from a component to context, and use the value in componentDidMount() method

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>
);
}
}

Resources