ReactJS infinite loop after react-select dropdown selection - reactjs

I just started teaching myself ReactJS a few weeks ago and I'm stuck trying to figure out why my API gets consistently hit in an infinite loop after selecting a value from a dropdown. I have a search component called StateSearch.js that is being rendered in the StatePolicyPage.js component.
In StatePolicyPage.js I call <StateSearch parentCallback={this.callbackFunction} /> so that I can get the value the user picked from the dropdown and set the state. In StateSearch.js I'm passing the selected value using props.parentCallback(response.data)
The problem is that an infinite loop occurs for some reason and my Rails API keeps getting called over and over instead of just returning the data one time.
(StateSearch.js) search component
import React, { useState } from 'react'
import Select from 'react-select'
import makeAnimated from 'react-select/animated';
import axios from 'axios';
import statesJSON from '../../helpers/states';
// uses 'react-select'
export default function StateSearch(props) {
const [americanState, setAmericanState] = useState();
// if a state was selected from the dropdown
if (americanState) {
axios.get("http://localhost:3001/get_stuff", {
params: {
state: americanState.value
}
}).then(response => {
// the response back from the Rails server
if (response.status === 200) {
props.parentCallback(response.data); // send data back up to parent
}
}).catch(error => {
console.log("Error fetching the state ", americanState.value, error);
})
event.preventDefault();
}
// the dropdown select box for states.
return (
<div>
<Select
options={statesJSON}
placeholder="Select a State"
onChange={setAmericanState}
noOptionsMessage={() => 'Uh-oh nothing matches your search'}
className=""
components={makeAnimated()}
isSearchable
isClearable={true}
/>
</div>
)
}
(StatePolicyPage.js) the component that the search results should be passed to
import React, { Component } from 'react'
import Navigation from './Navigation';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import StateSearch from './search/StateSearch';
export default class StatePolicyPage extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
stateName: '',
updatedAt: '',
createdAt: ''
}
}
callbackFunction = (childData) => {
console.log(childData);
this.setState({
id: childData.id,
stateName: childData.state_name,
updatedAt: childData.updated_at,
createdAt: childData.created_at
})
}
render() {
return (
<div>
<Navigation
isLoggedIn={this.props.loggedInStatus}
user={this.props.user}
handleLogoutClick={this.props.handleLogoutClick}
handleLogout={this.props.handleLogout}
/>
<Container>
{/* get the dropdown value from the StateSearch back */}
<StateSearch parentCallback={this.callbackFunction} />
<div>
<Row>
{ this.state.id }
</Row>
</div>
</Container>
</div>
)
}
}

Always use useEffect() hook for asynchronous tasks.
useEffect(() => {
// if a state was selected from the dropdown
if (americanState) {
axios.get("http://localhost:3001/get_stuff", {
params: {
state: americanState.value
}
}).then(response => {
// the response back from the Rails server
if (response.status === 200) {
props.parentCallback(response.data); // send data back up to parent
}
}).catch(error => {
console.log("Error fetching the state ", americanState.value, error);
})
}
}, [americanState]);

This line looks to me like it could cause an infinite loop:
components={makeAnimated()}
I'm not entirely sure what this function is doing, but when passing functions to another component you can't directly invoke them.
Try replacing the above line with this:
components={makeAnimated}
or with this:
components={() => makeAnimated()}

Related

React - Building a form to edit an existing product. Field contents are empty

I successfully built a form to create and save new products. Now I'm working on the page to modify previously created products. I can tell my API is returning data because the title shows up. However, it seems like the form is rendering before the state is populated and therefore the field is empty.
I think that the title is working because it's not reading from the state, it's reading directly from the product.name value which was returned from my API. However, for my form, I believe I need to point my field to the state which is empty at the moment the component rendered.
I'm just not sure how to only render the form once the API is done loading data and state is populated?
Any help greatly appreciated, have been trying to figure this out off and on for months.
I'm using redux toolkit: https://redux-toolkit.js.org/rtk-query/usage/queries#frequently-used-query-hook-return-values
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router';
import { useNavigate } from 'react-router-dom'
import AHeaderNav from '../../../../components/settings/AHeaderNav/AHeaderNav'
import './AdminProductEdit.scss';
import Wysiwyg from '../../../../components/global/Wysiwyg/Wysiwyg';
import SelectInput from '../../../../components/global/SelectInput/SelectInput'
import Breadcrumbs from '../../../../components/global/breadCrumbs/breadCrumbs';
import AdminBackButton from '../../../../components/settings/AdminBackButton/AdminBackButton'
import ImageC from '../../../../components/global/ImageC';
import { useGetProductAuthQuery, useUpdateProductMutation } from '../../../../api/apiSlice';
import { Spinner } from '../../../../components/global/Spinner/Spinner';
const AdminProductEdit = () => {
const { id: productId } = useParams()
const {
data: product = {},
isFetching,
isSuccess,
isLoadingCurrentPoduct } = useGetProductAuthQuery(productId, {
//pollingInterval: 3000,
refetchOnMountOrArgChange: true,
skip: false
})
const [updateProduct, { isLoading }] = useUpdateProductMutation()
const [productName, setProductName] = useState(product.productName)
const navigate = useNavigate()
const onProductNameChanged = e => setProductName(e.target.value)
if(!isSuccess) {
return <Spinner text="Loading..." />
} else {
const canSave = [productName].every(Boolean) && isSuccess && typeof productId === 'number'
const onSaveProductClicked = async () => {
if(canSave) {
try {
await updateProduct({productId,
productName,
productDescription,
productCategory,
shopNowUrl,
assetDescription})
navigate(`../settings/products`)
} catch (err) {
console.error(err)
}
}
}
return (
<div>
<AHeaderNav/>
<div className="topBannerImage">
<ImageC imageSrc={product.assetUrl}/>
</div>
<div className="contentWrapper">
<h3>Editting '{product.productName}'</h3>
<label>
Product Name
<input
type="text"
name="productName"
value={[productName]} // this is rendering an empty field.
// To me it seems like since this portion of the page is only
// supposed to be rendered once the API is successful, then
// this should be populated? What am I missing?
onChange={onProductNameChanged}
disabled={isLoading}
/>
</label>
<div className="button-group align-spaced">
<AdminBackButton/>
<button
className="submit button"
onClick={onSaveProductClicked}
disabled={!canSave}
>Save changes</button>
</div>
</div>
{spinner}
</div>
)
}
}
export default AdminProductEdit;

Using ReactJs to fetch data from an API but getting completely blank page with no errors

Guys Kindly i need your help. I am trying to fetch data from an Api and display it in the dom. I can see the data in the console but when i try to return data it shows a blank page and no errors. Below is my code.
App.js file
import React from "react";
import "./App.css";
import Movieapp from "./Movieapp";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
title: [],
date: [],
image: []
};
}
componentDidMount() {
fetch(`https://yts.mx/api/v2/list_movies.json?quality=3D`)
.then(res => res.json())
.then(data => {
console.log(data.data);
this.setState = {
title: data.data.movies[0].title,
date: data.data.movies[0].date_uploaded,
image: data.data.movies[0].background_image
};
});
}
render() {
return (
<div className="App">
<Movieapp
title={this.state.title}
date={this.state.date}
image={this.state.image}
/>
</div>
);
}
}
export default App;
Movieapp.js file
import React from "react";
const Movieapp = props => {
return (
<div>
<h1>{props.title}</h1>
<h1>{props.date}</h1>
<div>{props.image}</div>
</div>
);
};
export default Movieapp;
this.setState is a function, not a property. You have to use it properly:
this.setState({
title: data.data.movies[0].title,
date: data.data.movies[0].date_uploaded,
image: data.data.movies[0].background_image
});
Also, even though I guess you are just trying things our, there are few things to be aware of:
movies[0] can be undefined
You are getting multiple movies but showing only one. It's probably better to just save the whole data array in the state and iterate over the results in the render method

Stripe - how do I save card element in react?

I'm trying to save card details for use later.
I have generated the SetupIntent client secret
I'm trying to use confirm card setup.
I'm following the docs here for react.
The following line:
const cardElement = this.props.elements.getElement('card')
is throwing me this error:
TypeError: Cannot read property 'getElement' of undefined
Where am I going wrong? My code is below:
This is the relevant portion of the main component:
import React from "react";
import { Elements, StripeProvider } from "react-stripe-elements";
import SaveCardForm from "./SaveCardForm";
<StripeProvider
apiKey={process.env.REACT_APP_API_STRIPE_PUBLISH}
>
<Elements>
<SaveCardForm/>
</Elements>
</StripeProvider>
And this is the SaveCardForm component
import React, { Component } from "react";
import { Stripe, CardElement, injectStripe } from "react-stripe-elements";
import axios from "axios";
class SaveCardForm extends Component {
constructor(props) {
super(props);
this.submit = this.submit.bind(this);
}
submit = e => {
e.preventDefault()
const cardElement = this.props.elements.getElement('card');
axios.get(`${process.env.REACT_APP_API}/saveCardDetails`).then(res => {
console.log('res.data', res.data)
this.props.stripe.confirmCardSetup(res.data.client_secret, {
payment_method: {
card: cardElement,
},
}).then( confirmCardSetupRes => {
console.log('confirmCardSetupRes', confirmCardSetupRes)
})
})
}
render() {
return (
<div>
<CardElement />
<button onClick={this.submit}>
Bid For Tickets
</button>
</div>
);
}
}
export default injectStripe(SaveCardForm);
Given your components, there is no prop named elements passed into SaveCardForm. If it's access to CardElement you are after, use a ref which will give you a direct reference to that component e.g.
constructor(props) {
...
this.cardEl = React.createRef();
}
submit = e => {
...
const card = this.cardEl.current.<accessDomHere>;
this.props.stripe.confirmCardSetup(res.data.client_secret, {
payment_method: {
card
},
}).then(...)
}
render() {
...
<div>
<CardElement ref={this.cardEl} />
...
</div>
}
Switch out <accessDomHere> for whatever DOM query you need to perform to get the information you need. There may even be a React property or function you can access (I'm not familiar with the component).
I resolved this by updating to the latest version of react-stripe-elements.
There is an error in the versions before 5.1.0

How do I pass browser properties to react app talking to MSBOT?

We have hosted a bot on ServiceNow and would now like to pass attributes from the browser to the BOT. How can I make this happen?
This question is actually part 2 of a question I had posted & which I have already found a solution for.
Since the BOT is already logged into ServiceNow. I want to extract some elements from the background/servicenow page source and pass it to the react app as shown below. The BOT authenticates the user by email so it would act like a SSO because he is already connected to ServiceNow with the same email id. We therefore want to simply pass that value.
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
setTimeout(() => {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
value: {
language: window.navigator.language,
userid: "a.b#c.d",
username: "a.b#c.d"
}
}
});
}, 1000);
} else if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
if (action.payload.activity.from.role === 'bot') {
this.setState(() => ({ newMessage: true }));
}
}
return next(action);
});
You can pass the data from your page to your bot by dispatching an event listener on the page and catching the event in the web chat implementation.
In this example, I am simulating a user having logged in with a button click. The button click creates the event. When the event is registered, it is picked up by web chat which then takes the values stored in window.NOW.user and forwards that data to the bot. To help drive the point home, I am sending a message greeting the user by name while also sending the data (name and email) behind the scenes.
Hope of help!
app.js: Imports the view for display.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import WebChatView from './webChatView';
class App extends Component {
render() {
return (
<Router>
<div className="App">
<Route path="/" exact component={WebChatView} />
</div>
</Router>
);
}
}
export default App;
webChatView.js: I import the webchat component into the view and create a function that, on click (again, just to simulate someone having logged in), creates and dispatches an event.
import React, { Component } from 'react';
import WebChat from './webchat';
class WebChatView extends Component {
constructor(props) {
super(props);
this.sendToBot = this.sendToBot.bind(this);
}
render() {
return (
<div>
<div>
<WebChat id="webchat" role="main" />
</div>
<div>
<button id="loginBtn" onClick={this.sendToBot}>Login</button>
</div>
</div>
)
}
sendToBot = () => {
let sendToBotEvent = new Event('sendToBot')
window.dispatchEvent(sendToBotEvent);
window['NOW'] = {
'user': {
'name': 'John Doe',
'email': 'john.doe#somedomain.com'
}
}
}
}
export default WebChatView;
webchat.js: Lastly, is web chat, itself. I create a store and an event listener. When the event is dispatched in the window, the message/data is also dispatched to the bot. Because I'm simulating logging in as one step, I have included a setTimeout so the window.NOW.user data has a chance to save. The store is passed to <ReactWebChat> which, subsequently, sends the associated data to the bot for processing.
import React from 'react';
import ReactWebChat, { createDirectLine, createStore, } from 'botframework-webchat';
export default class WebChat extends React.Component {
constructor(props) {
super(props);
const store = window.WebChat.createStore();
window.addEventListener('sendToBot', () => {
setTimeout(() => {
store.dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'Service Now user name',
value: window.NOW.user
}
})
store.dispatch({
type: 'WEB_CHAT/SEND_MESSAGE',
payload: {
text: `Hi ${window.NOW.user.name}!`
}
})
}, 300)
})
}
this.state = {
store: store
};
componentWilUnmount() {
window.removeEventListener('sendToBot', null)
}
render() {
return (
this.state.directLine ?
<ReactWebChat
directLine={this.state.directLine}
store={this.state.store}
/>
:
<div>Connecting to bot…</div>
)
}
}

Load component on onClick of li in React

In my application, On the launch of the application, I am displaying a character from the provided JSON file.
Upon selecting a character the UI should update and display details on each of the films the character appears in. For that, I have made MovieDetails component which makes API calls ( API URL’s provided in the JSON file and the data returned from those calls ).
I am trying to fetch movie details when user click on first li (Luke Skywalker) for that I am using a switch case in handleClick(). But it's not working. Can anyone suggest me how to update the UI when the user click on li?
App.js
import React, { Component } from 'react'
import charactersFile from "./data/characters.json"
import MovieDetails from "./MovieDetails";
import './App.css';
class App extends Component {
state = {
render: false
}
handleClick = (character) => {
console.log(character.name);
this.setState({ render: true })
}
render() {
const list = <ul>
{
charactersFile.characters.map(character => {
return <li key={character.name} onClick={() => this.handleClick(character)}>{character.name}</li>
})
}
</ul>
return (
<React.Fragment>
{this.state.render ? list : <MovieDetails />}
</React.Fragment>
)
}
}
export default App
MovieDetails.js
import React, { Component } from 'react'
import axios from 'axios';
class MovieDetails extends Component {
state = {
movies: []
}
componentDidMount() {
const PeopleUrl = `https://swapi.co/api/people/`;
const FilmUrl = `https://swapi.co/api/films/`
axios.get(`${PeopleUrl}1/`)
.then(response => Promise.all([
axios.get(`${FilmUrl}2/`),
axios.get(`${FilmUrl}6/`),
axios.get(`${FilmUrl}3/`),
axios.get(`${FilmUrl}1/`),
axios.get(`${FilmUrl}7/`)
]))
.then(result => result.map(values =>
this.setState({
movies: [
...this.state.movies,
{ title: values.data.title, release_date: values.data.release_date }
]
})))
}
render() {
console.log(this.state.title)
return (
<div className="App">
<ul>
{this.state.movies.map(movie => (
<li key={movie.title}>
{movie.title} - {movie.release_date}
</li>
))}
</ul>
</div>
)
}
}
export default MovieDetails
characters.json
{
"characters": [
{
"name": "Luke Skywalker",
"url": "https://swapi.co/api/people/1/"
},
{
"name": "C-3PO",
"url": "https://swapi.co/api/people/2/"
},
{
"name": "Leia Organa",
"url": "https://swapi.co/api/people/unknown/"
},
{
"name": "R2-D2",
"url": "https://swapi.co/api/people/3/"
}
]
}
output:
Note: React only renders DOM elements inside a render function. If you return a components inside any other functions (in your case you are returning a component inside onClick function) you should not expect react renders a new content or a component.
Right now your onClick gets called upon a user's interaction. So you need to tell react to re-render the page and based on the current state renders different contents or components. As you probably know in order for react to re-render the page you need to set the state so inside your onClick
set your state to whatever your logic is this.setState({ ... }). Then inside your render function you can check the current state value that you just set inside onClick method whether to display <MovieDetails /> or something else.
Example: https://jsfiddle.net/pr0bwg3L/5/

Resources