I followed the below link and tried to do a poc https://www.reddit.com/r/reactjs/comments/82mxz3/how_to_make_an_api_call_on_props_change
In Searchbar I created a textbox, and I am getting the value but in index.js when I give componentWillRecieveProps and print the values to pass it to the api but nothing printing. Can you tell me how to fix it.Providing updated code sandbox and code snippet below.
https://codesandbox.io/s/eloquent-galielo-14874
//import React from "react";
import ReactDOM from "react-dom";
import React, { Fragment, useState, Component } from "react";
import "./styles.css";
class SearchBar extends Component {
state = {
groupCheckBoxValues: [],
groupRadioValue: "PRO"
};
getInitialState() {
return { value: "Hello!" };
}
handleChange(event) {
console.log("handleChange", event.target.value);
this.setState({ value: event.target.value });
}
componentWillReceiveProps({ search }) {
console.log(search);
}
componentDidMount() {
this.fetchdata("story");
}
fetchdata(type = "", search_tag = "") {
var url = "https://hn.algolia.com/api/v1/search?tags=";
fetch(`${url}${type}&query=${search_tag}`)
.then(res => res.json())
.then(data => {
this.props.getData(data.hits);
});
}
render() {
return (
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
);
}
}
export default SearchBar;
You're not actually passing the text input's value to the fetch request.
I recommend something like this:
class SearchBar extends Component {
searchByKeyword = ({target}) => {
await this.getQuery("story", target.value)
}
async componentDidMount() {
await this.getQuery("story", "butts");
}
getQuery = async(type = "", search_tag = "") => {
var url = "https://hn.algolia.com/api/v1/search?tags=";
const resp = await fetch(`${url}${type}&query=${search_tag}`)
return resp.json()
}
render() {
return (
<input
type="text"
onChange={this.searchByKeyword}
/>
);
}
}
I removed the state and things because it doesn't seem entirely germane to the question.
There're some mistakes in your demo.
The function setState function won't work cause you didn't bind it to your component.
The UNSAFE_componentWillReceiveProps() is unsafe function. It will be deprecated soon.
And you shouldn't use { useState } hook in Class Component.
I think you should check some tutorials for fetching data from reactjs site. It will provide some good practices for you :)
Related
Very simple app, I'm trying to display content from my API using Mobx and Axios, here's my Axios agent.ts:
import { ITutorialUnit } from './../model/unit';
import axios, { AxiosResponse } from "axios";
//set the base URL
axios.defaults.baseURL = "http://localhost:5000/api";
//store our request in a const
const responseBody = (response: AxiosResponse) => response.data;
const requests = {
get: (url: string) => axios.get(url).then(responseBody),
};
//create a const for our activty's feature,all our activities' request are go inside our Activities object
const TutorialUnits = {
list: ():Promise<ITutorialUnit[]> => requests.get("/tutorialunits"),
};
export default{
TutorialUnits
}
then I call this agent.s in a store:
import { ITutorialUnit } from "./../model/unit";
import { action, observable } from "mobx";
import { createContext } from "react";
import agent from "../api/agent";
class UnitStore {
#observable units: ITutorialUnit[] = [];
//observable for loading indicator
#observable loadingInitial = false;
#action loadUnits = async () => {
//start the loading indicator
this.loadingInitial = true;
try {
//we use await to block anything block anything below list() method
const units = await agent.TutorialUnits.list();
units.forEach((unit) => {
this.units.push(unit);
// console.log(units);
});
this.loadingInitial = false;
} catch (error) {
console.log(error);
this.loadingInitial = false;
}
};
}
export default createContext(new UnitStore());
then I call this in my App component:
import React, { Fragment, useContext, useEffect } from "react";
import { Container } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import NavBar from "../../features/nav/NavBar";
import { ActivityDashboard } from "../../features/Units/dashboard/tutorialUnitDashboard";
import UnitStore from "../stores/unitStore";
import { observer } from "mobx-react-lite";
import { LoadingComponent } from "./LoadingComponent";
const App = () => {
const unitStore = useContext(UnitStore);
useEffect(() => {
unitStore.loadUnits();
//need to specify the dependencies in dependenciy array below
}, [unitStore]);
//we are also observing loading initial below
if (unitStore.loadingInitial) {
return <LoadingComponent content="Loading contents..." />;
}
return (
<Fragment>
<NavBar />
<Container style={{ marginTop: "7em" }}>
<ActivityDashboard />
</Container>
</Fragment>
);
};
export default observer(App);
Finally, I want to use this component to display my content:
import { observer } from "mobx-react-lite";
import React, { Fragment, useContext } from "react";
import { Button, Item, Label, Segment } from "semantic-ui-react";
import UnitStore from "../../../app/stores/unitStore";
const UnitList: React.FC = () => {
const unitStore = useContext(UnitStore);
const { units } = unitStore;
console.log(units)
return (
<Fragment>
{units.map((unit) => (
<h2>{unit.content}</h2>
))}
</Fragment>
);
};
export default observer(UnitList);
I can't see the units..
Where's the problem? My API is working, I tested with Postman.
Thanks!!
If you were using MobX 6 then you now need to use makeObservable method inside constructor to achieve same functionality with decorators as before:
class UnitStore {
#observable units: ITutorialUnit[] = [];
#observable loadingInitial = false;
constructor() {
// Just call it here
makeObservable(this);
}
// other code
}
Although there is new thing that will probably allow you to drop decorators altogether, makeAutoObservable:
class UnitStore {
// Don't need decorators now anywhere
units: ITutorialUnit[] = [];
loadingInitial = false;
constructor() {
// Just call it here
makeAutoObservable(this);
}
// other code
}
More info here: https://mobx.js.org/react-integration.html
the problem seems to be the version, I downgraded my Mobx to 5.10.1 and my mobx-react-lite to 1.4.1 then Boom everything's fine now.
How do I create a component for Gatsby that will load on the client-side, not at build time?
I created this one and it renders with gatsby develop but not with the rendered server-side rendering
import React from 'react';
import axios from 'axios';
import adapter from 'axios-jsonp';
export default class Reputation extends React.Component<{}, { reputation?: number }> {
constructor(props) {
super(props);
this.state = {};
}
async componentDidMount() {
const response = await axios({
url: 'https://api.stackexchange.com/2.2/users/23528?&site=stackoverflow',
adapter
});
if (response.status === 200) {
const userDetails = response.data.items[0];
const reputation = userDetails.reputation;
this.setState({
reputation
});
}
}
render() {
return <span>{ this.state.reputation?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") }</span>
}
}
If you don't want the component to be bundled in the main js file at build time, use loadable-components
Install loadable-components and use it as a wrapper for a component that wants to use a client-side only package. docs
import React, { Component } from "react";
import Loadable from "#loadable/component";
const LoadableReputation = Loadable(() =>
import("../components/Reputation")
);
const Parent = () => {
return (
<div>
<LoadableReputation />
</div>
);
};
export default Parent;
before render this component, make sure you have a window, to detect if there is a window object. I would write a hook for that:
function hasWindow() {
const [isWindow, setIsWindow] = React.useState(false);
React.useEffect(() => {
setIsWindow(true);
return ()=> setIsWindow(false);
}, []);
return isWindow;
}
In the parent component check if there is a window object:
function Parent(){
const isWindow = hasWindow();
if(isWindow){
return <Reputation />;
}
return null;
}
I'm new to React and Redux and I'm trying to build a website. I'm using Material-UI components. The AppSearchBarInput is an input in the header where users can search for an id (referred to as appId in the code). If appId is found in the database then I want to redirect to another page (this.props.history.push('/app/appInfo');). If the input is blank then I display a snackbar (AppNotFound) with warning message as well as when the input doesn't exist in the database.
In the onKeyDown method I use connect to dispatch an action which checks if the id exists in the database (dispatch is called from getAppInfo using redux-thunk). Then I need to immediately get the new Redux state via props in order to decide whether the id was found or not and depending on this I set snackBarOpen local state property to true or false.
The problem is that I can't get the updated props within the same method when calling dispatch. I could use componentDidUpdate but then I can't update local store from it. Is there some workaround for this situation either using some react lifecycle method or redux trick?
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import InputBase from '#material-ui/core/InputBase';
import { withStyles } from '#material-ui/core/styles';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { getAppInfo } from '../../actions/appActions.js';
import constants from '../../constants.js';
import { AppSearchBarInputStyles } from '../styles/Material-UI/muiStyles.js';
import AppNotFound from './AppNotFound.js';
import * as log from 'loglevel';
log.setLevel("debug")
class AppSearchBarInput extends Component {
state = {
appId: '',
snackBarOpen: false
}
onChange = e => {
this.setState({ appId: e.target.value });
}
onKeyDown = e => {
const { appId } = this.state;
if (e.keyCode === constants.ENTER_KEY) {
if (appId.trim() !== '') {
this.props.getAppInfo({ appId });
this.setState({
appId: ''
});
const { found } = this.props.app; // ! problem here !
if (found) {
this.props.history.push('/app/appInfo');
} else {
this.setState({
snackBarOpen: true
});
}
} else {
this.setState({
snackBarOpen: true
});
}
}
}
handleCloseSnackBar = () => {
this.setState({
snackBarOpen: false
});
}
render() {
const { classes } = this.props;
const { appId, snackBarOpen } = this.state;
const { found } = this.props.app;
let message = '';
if (!found) {
message = appId === '' ? constants.MESSAGES.APP_BLANK() : constants.MESSAGES.APP_NOT_FOUND(appId);
}
return (
<div>
<InputBase
placeholder="Search…"
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
value={this.state.appId} />
<AppNotFound message={message}
open={snackBarOpen && !found}
onClose={this.handleCloseSnackBar}/>
</div>
)
}
}
AppSearchBarInput.propTypes = {
classes: PropTypes.object.isRequired
}
const mapStateToProps = state => ({
app: state.app.app
});
const AppSearchBarWithStyles = withStyles(AppSearchBarInputStyles)(AppSearchBarInput);
const AppSearchBarWithStylesWithRouter = withRouter(AppSearchBarWithStyles);
export default connect(mapStateToProps, { getAppInfo })(AppSearchBarWithStylesWithRouter);
From the look of your code you have no dispatch method. If data was really fetched, then you're not getting updates because withRouter actually blocks shouldComponentUpdate. If you want updated state, wrap the connect in withRouter.
You should also have a mapDispatchToProps function. Like this:
const mapDispatchToProps = dispatch => ({
makeDispatch: () => dispatch({ getAppInfo })
})
So, instead of:
const AppSearchBarWithStyles = withStyles(AppSearchBarInputStyles)(AppSearchBarInput);
const AppSearchBarWithStylesWithRouter = withRouter(AppSearchBarWithStyles);
export default connect(mapStateToProps, { getAppInfo })(AppSearchBarWithStylesWithRouter);
You should have:
const AppSearchBarWithStyles = withStyles(AppSearchBarInputStyles)(AppSearchBarInput);
AppSearchBarWithStylesWithConnect = connect(mapStateToProps, mapDispatchToProps)(AppSearchBarWithStyles);
export default withRouter(AppSearchBarWithStylesWithConnect);
Please see this: https://reacttraining.com/react-router/core/api/withRouter
I want to have an auto completing location search bar in my react component, but don't know how I would go about implementing it. The documentation says to include
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap" async defer></script>
in an HTML file, and then have an initialize function pointing to an element - how would I go about doing this with my react component/JSX? I presume I would have to import the api link, but I have no clue where to go from there.
import React from 'react';
import "https://maps.googleapis.com/maps/api/js?key=MYKEY&libraries=places&callback=initMap";
const SearchBar = () => (
<input type="text" id="search"/> //where I want the google autocomplete to be
);
export default SearchBar;
Google Maps API loading via static import:
import "https://maps.googleapis.com/maps/api/js?key=MYKEY&libraries=places&callback=initMap";
is not supported, you need to consider a different options for that purpose:
reference Google Maps API JS library via /public/index.html file:
<script src="https://maps.googleapis.com/maps/api/js?key=MYKEY&libraries=places"></script>
or dynamically load JS resource, for example using this
library
Now regarding SearchBar component, the below example demonstrates how to implement a simple version of Place Autocomplete (without a dependency to Google Map instance) based on this official example
import React from "react";
/* global google */
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.autocompleteInput = React.createRef();
this.autocomplete = null;
this.handlePlaceChanged = this.handlePlaceChanged.bind(this);
}
componentDidMount() {
this.autocomplete = new google.maps.places.Autocomplete(this.autocompleteInput.current,
{"types": ["geocode"]});
this.autocomplete.addListener('place_changed', this.handlePlaceChanged);
}
handlePlaceChanged(){
const place = this.autocomplete.getPlace();
this.props.onPlaceLoaded(place);
}
render() {
return (
<input ref={this.autocompleteInput} id="autocomplete" placeholder="Enter your address"
type="text"></input>
);
}
}
Here's a solution using ES6 + React Hooks:
First, create a useGoogleMapsApi hook to load the external script:
import { useEffect, useState, useCallback } from 'react'
import loadScript from 'load-script'
import each from 'lodash/each'
var googleMapsApi
var loading = false
var callbacks = []
const useGoogleMapsApi = () => {
const [, setApi] = useState()
const callback = useCallback(() => {
setApi(window.google.maps)
}, [])
useEffect(() => {
if (loading) {
callbacks.push(callback)
} else {
if (!googleMapsApi) {
loading = true
loadScript(
`https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}&libraries=places`,
{ async: true },
() => {
loading = false
googleMapsApi = window.google.maps
setApi(window.google.maps)
each(callbacks, init => init())
callbacks = []
})
}
}
}, [])
return googleMapsApi
}
export default useGoogleMapsApi
Then, here's your input component:
import React, { useRef, useEffect, forwardRef } from 'react'
import useGoogleMapsApi from './useGoogleMapsApi'
const LocationInput = forwardRef((props, ref) => {
const inputRef = useRef()
const autocompleteRef = useRef()
const googleMapsApi = useGoogleMapsApi()
useEffect(() => {
if (googleMapsApi) {
autocompleteRef.current = new googleMapsApi.places.Autocomplete(inputRef.current, { types: ['(cities)'] })
autocompleteRef.current.addListener('place_changed', () => {
const place = autocompleteRef.current.getPlace()
// Do something with the resolved place here (ie store in redux state)
})
}
}, [googleMapsApi])
const handleSubmit = (e) => {
e.preventDefault()
return false
}
return (
<form autoComplete='off' onSubmit={handleSubmit}>
<label htmlFor='location'>Google Maps Location Lookup</label>
<input
name='location'
aria-label='Search locations'
ref={inputRef}
placeholder='placeholder'
autoComplete='off'
/>
</form>
)
}
export default LocationInput
Viola!
Was making a custom address autocomplete for a sign up form and ran into some issues,
// index.html imports the google script via script tag ie: <script src="https://maps.googleapis.com/maps/api/js?key=MYKEY&libraries=places"></script>
import {useState, useRef, useEffect } from 'React'
function AutoCompleteInput(){
const [predictions, setPredictions] = useState([]);
const [input, setInput] = useState('');
const [selectedPlaceDetail, addSelectedPlaceDetail] = useState({})
const predictionsRef = useRef();
useEffect(
()=>{
try {
autocompleteService.current.getPlacePredictions({ input }, predictions => {
setPredictions(predictions);
});
} catch (err) {
// do something
}
}
}, [input])
const handleAutoCompletePlaceSelected = placeId=>{
if (window.google) {
const PlacesService = new window.google.maps.places.PlacesService(predictionsRef.current);
try {
PlacesService.getDetails(
{
placeId,
fields: ['address_components'],
},
place => addSelectedPlaceDetail(place)
);
} catch (e) {
console.error(e);
}
}
}
return (
<>
<input onChange={(e)=>setInput(e.currentTarget.value)}
<div ref={predictionsRef}
{ predictions.map(prediction => <div onClick={ ()=>handleAutoCompletePlaceSelected(suggestion.place_id)}> prediction.description </div> )
}
</div>
<>
)
}
So basically, you setup the autocomplete call, and get back the predictions results in your local state.
from there, map and show the results with a click handler that will do the follow up request to the places services with access to the getDetails method for the full address object or whatever fields you want.
you then save that response to your local state and off you go.
I'm currently using Flickr api to make a Simple Image Carousel and facing a problem where my state does not get updated or rendered whenever I click the button.
Here is my index.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
import _ from 'lodash';
import Photo from './components/photo';
const urlArr = [];
const apiKey = "API";
const userId = "ID";
const url = `https://api.flickr.com/services/rest/?method=flickr.people.getPublicPhotos&api_key=${apiKey}&user_id=${userId}&format=json&nojsoncallback=1`;
class App extends Component {
constructor(props) {
super(props);
this.state = { urlArr: [] };
axios.get(url)
.then((photoData) => {
_.forEach(photoData.data.photos.photo, (photo) => {
urlArr.push(`https://farm6.staticflickr.com//${photo.server}//${photo.id}_${photo.secret}_z.jpg`);
});
this.setState({ urlArr });
});
}
render() {
return (
<div>
<Photo urls={this.state.urlArr}/>
</div>
);
}
};
ReactDOM.render(<App/>, document.querySelector('.container'));
and here is my photo.js
import React, { Component } from 'react';
import NextButton from './nextButton';
import PrevButton from './prevButton';
class Photo extends Component {
constructor(props) {
super(props);
this.state = { idx: 0 };
this.nextImg = this.nextImg.bind(this);
}
nextImg() {
this.setState({ idx: this.state.idx++ });
}
render() {
if (this.props.urls.length === 0) {
return <div>Image Loading...</div>
}
console.log(this.state);
return(
<div>
<PrevButton />
<img src={this.props.urls[this.state.idx]}/>
<NextButton onClick={this.nextImg}/>
</div>
);
}
}
export default Photo;
and my nextButton.js (same as prevButton.js)
import React from 'react';
const NextButton = () =>{
return (
<div>
<button>next</button>
</div>
);
};
export default NextButton;
Since I'm fairly new to React, I'm not quite sure why my this.state.idx is not getting updated when I click on the next button (Seems to me that it is not even firing nextImg function either). If anyone can give me a help or advice, that would really appreciated.
Thanks in advance!!
Update your NextButton. Use the event within your presentational component.
<NextButton next={this.nextImg}/>
And the NextButton component should looks like this.
import React from 'react';
const NextButton = (props) =>{
return (<div>
<button onClick={props.next}>next</button>
</div>
);
};
The problem lies with this piece of code:
axios.get(url)
.then((photoData) => {
_.forEach(photoData.data.photos.photo, (photo) => {
urlArr.push(`https://farm6.staticflickr.com//${photo.server}//${photo.id}_${photo.secret}_z.jpg`);
});
this.setState({ urlArr });
});
this refers to the axios.get callback scope and not the Component. You can define another variable called self or something that makes more sense to you and call self.setState().
See this question for a similar problem: Javascript "this" scope