How Do I Pass State (Value) when Calling Component in React? - javascript

I have a React APP which displays images perfectly fine from the Flickr API based on a 'dogs' query which is built into the code--this is container which accomplishes this:
import React, { Component } from 'react';
import axios from 'axios';
import apiKey from './config.js';
import Navigation from './Navigation.js';
import Photos from './Photos.js';
import SearchBar from './SearchBar.js';
class Container extends Component {
constructor (){
super();
this.state = {
imgs:[],
query: '',
loading: true
}
}
componentDidMount() {
this.performSearch();
}
performSearch = (query = 'dogs') => {
axios.get(`https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=${apiKey}&tags=${query}&per_page=24&format=json&nojsoncallback=1`)
.then(response => {
this.setState({
imgs: response.data.photos.photo,
loading: false
});
})
.catch(err => {
console.log('Error happened during fetching!', err);
});
}
render() {
return (
<div className="container">
<SearchBar onSearch={this.performSearch}/>
<Navigation />
{
(this.state.loading)
? <p>Loading....</p>
: <Photos data={this.state.imgs}/>
}
</div>
);
}
}
export default Container;
I have a nav bar with three different links--and I would like to call the Container component for each-- passing a specific value for the query in the API.
So for example, if my links are bears, cats, and mice, I would like the respective link to generate the correct api based on the query term.
I am just uncertain as to how I pass the query term utilizing the Container component. Go easy, as I'm a bit new to react, but I was trying something like
<Container query="bears" />
or
<Container performSearch("bears") />
But I can't seem to figure out how to pass the appropriate value.

It looks like you want to pass the query in as a prop. I would begin by updating the constructor to receive props, pass them to super(), and use them to set the value of the state's query property:
constructor(props) {
super(props);
this.state = {
imgs: [],
query: this.props.query,
loading: true
}
}
Next, update the performSearch() method to use the state's query property instead of the hard-coded 'dogs' value:
performSearch = (query = this.state.query) => {
Now you can pass the appropriate query property to the Container like you were thinking:
<Container query="bears" />
<Container query="cats" />
<Container query="mice" />

Related

Why won't my array use the map method even though it's actually an array?

So I console.log the state of users after I set it as an array of objects from the github api and it displays as an array. However, I can't use the map function like this (if I decided to use it in a child component using props):
this.state.users.map((user)=>(
<UserItem key={user.id} user={user}/>
))
I get an error: "TypeError: this.state.users.map is not a function"
And It's pretty confusing because it is an array. It looks like this in the props viewer:
As you can see. Users is an array. I don't understand but I could be missing something very simple from lack of coffee. Thanks for your help! I appreciate it.
import React, { Component } from 'react';
import Navbar from './components/layout/Navbar';
// import UserItem from './components/users/UserItem';
import Users from './components/users/Users';
// import './App.css';
import axios from 'axios';
class App extends Component {
state = {
users: {},
loading: false
}
async componentDidMount() {
this.setState(
{loading: true}
)
const res = await axios.get('https://api.github.com/users')
this.setState(
{
loading: false,
users: res.data,
}
)
// console.log(res.data)
}
render() {
return (
<React.Fragment>
<Navbar />
<div className="container">
<Users loading={this.state.loading} users={this.state.users} />
</div>
</React.Fragment>
);
}
}
export default App;
The initial value of this.state.users is an object. Hence the error map is not a function
state = {
users: {}, <--- object
loading: false
}
The API call happens after the component has mounted for the first time in the componentDidMount life cycle method and the value of state.users gets populated with the data.
The component has to render with the initial state on the first render.
Change the value of users to be an empty array in the initial state.
state = {
users: [], // change initial value of users to be an empty array
loading: false
}

React - Ajax data not being passed into Child Component

I have two components, one parent one child. I am using the fetch method in componentDidMount() callback. Once I do this, I set the state with key items to that data that is pulled from the api. Once I do this it should be able to be console logged in the child component as a prop. However this is not working. What am I doing wrong here?
Parent Component:
import React, {Component} from 'react';
import Map from './maps/Map';
class Main extends Component {
constructor(props){
super(props);
this.state = {
name: "John",
items: []
}
}
componentDidMount() {
fetch('https://hn.algolia.com/api/v1/search?query=')
.then(dat => dat.json())
.then(dat => {
this.setState({
items: dat.hits
})
})
}
render() {
return (
<div>
<Map list={this.state.name} items={this.state.items}></Map>
</div>
)
}
}
export default Main;
Child Component:
import React, {Component} from 'react';
class Map extends Component {
constructor(props) {
super(props);
console.log(props.items)
}
render () {
return (
<h1>{this.props.name}</h1>
)
}
}
export default Map;
First, fetch is asynchronous. So, the fetch statement might be pending by the time you try to console.log the result inside the child constructor.
Putting the console.log inside the render method would work, because the component will be rerendered, if the state items changes.
The constructor for a component only runs one time during a lifecycle. When it does, props.items is undefined because your ajax request is in-flight, so console.log(props.items) doesn't show anything.
If you change your constructor to console.log("constructed");, you'll see one-time output (stack snippets may not show this--look in your browser console). Henceforth, componentDidUpdate() can be used to see the new props that were set when your ajax request finishes.
You could also log the props inside the render method, which will run once before the ajax request resolves and again afterwards when props.items changes.
As a side point, you have <Map list=... but the component tries to render this.props.name, which is undefined.
Also, if you aren't doing anything in the constructor (initializing state or binding functions) as here, you don't need it.
class Map_ /* _ added to avoid name clash */ extends React.Component {
constructor(props) {
super(props);
console.log("constructed");
}
componentDidUpdate(prevProps, prevState) {
const props = JSON.stringify(this.props, null, 2);
console.log("I got new props", props);
}
render() {
return (
<div>
<h1>{this.props.name}</h1>
<pre>
<ul>
{this.props.items.map((e, i) =>
<li key={i}>{JSON.stringify(e, null, 2)}</li>)}
</ul>
</pre>
</div>
);
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {name: "John", items: []};
}
componentDidMount() {
fetch('https://hn.algolia.com/api/v1/search?query=')
.then(dat => dat.json())
.then(dat => {
this.setState({items: dat.hits})
});
}
render() {
return (
<div>
<Map_
name={this.state.name}
items={this.state.items}
/>
</div>
);
}
}
ReactDOM.createRoot(document.querySelector("#app"))
.render(<Main />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="app"></div>
The only problem you have is that you are trying to use this.props.name and your Map component props are called list and items, so it will return undefined.
If you log your props in the constructor you will get the initial state of Main because the fetch hasn't returned anything yet. Remember that the constructor only runs once. So you are probably getting an empty array when you log props.items in the constructor because that's what you have in your initial state.
{
name: "John",
items: []
}
If you log the props in your render method you will see your array filled with the data you fetched, as you can see here:
https://codesandbox.io/s/stoic-cache-m7d43
If you don't want to show the component until the data is fetched you can include a boolean property in your state that you set to true once you the fetch returns a response and pass it as a prop to your component. Your component can you use that variable to show, for example, a spinner while you are fetching the data. Here's an example:
https://codesandbox.io/s/reverent-edison-in9w4
import CircularProgress from "#material-ui/core/CircularProgress"
class Main extends Component {
constructor(props) {
super(props);
this.state = {
name: "John",
items: [],
fecthed: false
};
}
componentDidMount() {
fetch("https://hn.algolia.com/api/v1/search?query=")
.then(dat => dat.json())
.then(dat => {
this.setState({
items: dat.hits,
fecthed: true
});
});
}
render() {
return (
<Map
fetched={this.state.fecthed}
list={this.state.name}
items={this.state.items}
/>
);
}
}
class Map extends Component {
render() {
return (
<div>
{this.props.fetched ? (
<div>
<h1>{this.props.list}</h1>
{this.props.items.map((item, indx) => (
<div key={indx}>Author: {item.author}</div>
))}
</div>
) : (
<CircularProgress />
)}
</div>
);
}
}
Hope this helps. Cheers!

Refresh table after post in react

After a post I would like to reload my table to be able to display the data after the post. Now the question arises how to get my "DataProvider" to render again?
I would do this as a function call in "FormOPCConnect". But I don't know how to start. I already tried to use the "props" of the "DataProvider", but I can't figure out how to render the new table.
Enclosed my source code.
TableOPCConnections.js
import React from "react";
import PropTypes from "prop-types";
import key from "weak-key";
import Table from 'react-bootstrap/Table'
const OPCVarTable = ({ data }) =>
!data.length ? (
<p>Nothing to show</p>
) : (
<div>
<h2 className="subtitle">
Showing <strong>{data.length}</strong> OPC Variables
</h2>
<Table striped bordered hover>
<thead>
<tr>
{Object.entries(data[0]).map(el => <th key={key(el)}>{el[0]}</th>)}
</tr>
</thead>
<tbody>
{data.map(el => (
<tr key={el.id}>
{Object.entries(el).map(el => <td key={key(el)}>{el[1]}</td>)}
</tr>
))}
</tbody>
</Table>
</div>
);
OPCVarTable.propTypes = {
data: PropTypes.array.isRequired
};
export default OPCVarTable;
DataProvider.js
import React, { Component } from "react";
import PropTypes from "prop-types";
class DataProvider extends Component {
static propTypes = {
endpoint: PropTypes.string.isRequired,
render: PropTypes.func.isRequired
};
state = {
data: [],
loaded: false,
placeholder: "Loading..."
};
componentDidMount() {
fetch(this.props.endpoint)
.then(response => {
if (response.status !== 200) {
return this.setState({ placeholder: "Something went wrong" });
}
return response.json();
})
.then(data => this.setState({ data: data, loaded: true }));
}
render() {
const { data, loaded, placeholder } = this.state;
return loaded ? this.props.render(data) : <p>{placeholder}</p>;
}
}
export default DataProvider;
FormOPCConnect.js
(Here I'd like to refresh the state of the DataProvider)
After the fetch method I would like to render the table again as long as the post to the database was successful.
import React, { Component, useState } from "react";
import PropTypes from "prop-types";
import Button from "react-bootstrap/Button";
import Form from 'react-bootstrap/Form'
import DataProvider from "./DataProvider";
import csrftoken from './csrftoken';
class FormOPCConnect extends Component {
constructor(props) {
super(props);
this.state = {
validated: false
};
}
static propTypes = {
endpoint: PropTypes.string.isRequired
};
state = {
ip_address: "",
port: "",
namespace_name: "",
root_name: "",
owner: ""
};
handleChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
handleSubmit = event => {
const form = event.currentTarget;
if (form.checkValidity() === false) {
event.preventDefault();
event.stopPropagation();
}
event.preventDefault();
const { ip_address, port, namespace_name, root_name, owner } = this.state;
const opcConn= { ip_address, port, namespace_name, root_name, owner };
const conf = {
method: "post",
body: JSON.stringify(opcConn),
headers: new Headers({ "Content-Type": "application/json", "X-CSRFTOKEN": csrftoken })
}
fetch(this.props.endpoint, conf).then(response => console.log(response));
//>>
//if response is valid -> refresh the Dataprovider and the table...
//<<
this.setState({ validated: this.state.validated = true })
};
App.js
const App = () => (
<React.Fragment>
<Container>
<Row>
<Col> <NavBarTop fixed="top" /> </Col>
</Row>
<Row>
<Col> <DataProvider endpoint="opcconnection/"
render={data => <OPCVarTable data={data} />} /></Col>
<Col><FormOPCConnect endpoint="opcconnection/" /></Col>
</Row>
</Container>
</React.Fragment>
);
const wrapper = document.getElementById("app");
wrapper ? ReactDOM.render(<App />, wrapper) : null;
I'm new to React, so please forgive my mistakes. :D
Finally it looks like this.
OPCConnection_Image
Your code currently contains 2 problems that need to be fixed in order to update your table when you post data.
1)
Your DataProvider does actually rerender on props change. The problem here is that your logic to fetch your data is in componentDidMount. componentDidMount only triggers the first time the component mounts and doesn't trigger on rerender.
If you want your data to fetch everytime the component rerenders you could place your fetch functionality in your render method of DataProvider.
To rerender a component all you have to do is update its props or it's state.
2) You want your DataProvider to update when your FormOPCConnect has finished some logic.
The thing with React is. You can only pass variables from parents to children. You can't directly communicate from sibling to sibling or from child to parent.
In your App your DataProvider is a siblign of FormOPCConnect, they are next to eachother.
<App> // App can communicate with every component inside it.
<DataProvider /> // This component can't communicate with the component next to it.
<FormOPCConnect />
</App>
The easiest thing to do here would be to either render DataProvider inside FormOPCConnect and update DataProvider's props directly.
Or if that is not possible, keep a state in App which checks if your logic in FormOPCConnect has finished.
class App extends Component {
constructor(props) {
super(props);
this.state = { boolean: false }; //state is remembered when a component updates.
}
flipBoolean() { //this function updates the state and rerenders App when called.
this.setState(
boolean: boolean!
);
};
render {
return (
<Fragment>
<DataProvider />
<FormOPCConnect flipBoolean={this.flipBoolean} />
</Fragment>
)
}
}
Pass a function to FormOPCConnect which updates the state of App. When you want your DataProvider to rerender you simply call that flipBoolean function in FormOPCConnect. This will update the state in App. Which will trigger App to rerender. Which will in it's turn rerender it's children DataProvider andFormOPCConnect`.
(This variable doesn't need to be a boolean, you can do here whatever you want. This boolean is just an example).

How to transfer data between pages in Reactjs

I am trying to transfer data back to my class component from DropDownItem component and I can't do it with props.
in this example, I want to transfer props.product of the clicked item to my App Component.
could anyone tell me what is the best way to do it?
thanks in advance.
when I'm clicking on a drop down option I want to show it as an instance in my
main class component.
I already tried to create a special component for this purpose, but it makes the code really complicated.
class App extends Component {
constructor(props) {
super(props)
this.state = {
products: []
}
}
async fetchDevices() {
const url = 'my url';
const response = await fetch(url, { method: 'GET', headers: headers });
const data = await response.json()
this.setState({ products: data.value })
}
componentDidMount() {
this.fetchDevices();
}
render() {
const productItems = this.state.products.map((productValue, index) => {
return (<DropDownItem key={index} product={productValue.name} />)
})
return (
<div>
{this.state.products[0] && Array.isArray(this.state.products) ?
<div>
<DropdownComponent
isOpen={this.state.dropdownOpen}
toggle={this.toggle}
product={productItems}
/>
</div> :
<div>loading...</div>}
<p></p>
</div>
)
}
}
export default App
function DropDownItem(props) {
return (
<div>
<DropdownItem onClick={() => console.log("selected product", props.product)}>
{props.product}</DropdownItem>
<DropdownItem divider />
</div>
)
}
export default DropDownItem
I want to show the selected item in my App component
The general rules for data transfer between components are:
To transfer from parent component (App in your case) to child (DropDownItem) use props
To transfer from child to parent (what you want to do) - use callbacks with relevant parameters
Example (not tested, just to show idea)
// App component
...
selectDropDownItem(item) {
this.setState({selectedItem: item});
}
render () {
return
// ...
<DropdownComponent
isOpen={this.state.dropdownOpen}
toggle={this.toggle}
product={productItems}
selectItemCallback={this.selectDropDownItem.bind(this)}/>
//...
// DropDownItem component
function DropDownItem (props) {
return (
<div>
// bind to props.product so when called props.product will be passed as argument
<DropdownItem onClick={props.selectItemCallback.bind (null, props.product)}>
{props.product}</DropdownItem>
<DropdownItem divider/>
</div>
)
}
export default DropDownItem
General idea is that you pass callback from your parent down to child. When child needs to communicate to parent it just calls callback passed in props and pass required data as arguments to this callback (in your example this can be selected item)

ReactJS: I can not get the state to update with API data when the asynchronous API data fetch is completed

I am having a bit of an issue rendering components before the state is set to the data from a returned asynchronous API request. I have a fetch() method that fires off, returns data from an API, and then sets the state to this data. Here is that block of code that handles this:
class App extends Component {
constructor() {
super();
this.state = {
currentPrice: null,
};
}
componentDidMount() {
const getCurrentPrice = () => {
const url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
fetch(url).then(data => data.json())
.then(currentPrice => {
this.setState = ({
currentPrice: currentPrice.bpi.USD.rate
})
console.log('API CALL', currentPrice.bpi.USD.rate);
}).catch((error) => {
console.log(error);
})
}
getCurrentPrice();
}
You will notice the console.log('API CALL', currentPrice.bpi.USD.rate) that I use to check if the API data is being returned, and it absolutely is. currentPrice.bpi.USD.rate returns an integer (2345.55 for example) right in the console as expected.
Great, so then I assumed that
this.setState = ({ currentPrice: currentPrice.bpi.USD.rate }) should set the state without an issue, since this data was received back successfully.
So I now render the components like so:
render() {
return (
<div>
<NavigationBar />
<PriceOverview data={this.state.currentPrice}/>
</div>
);
}
}
export default App;
With this, I was expecting to be able to access this data in my PriceOverview.js component like so: this.props.data
I have used console.log() to check this.props.data inside my PriceOverview.js component, and I am getting 'null' back as that is the default I set intially. The issue I am having is that the components render before the API fetch has ran it's course and updated the state with the returned data. So when App.js renders the PriceOverview.js component, it only passes currentPrice: null to it, because the asynchronous fetch() has not returned the data prior to rendering.
My confusion lies with this.setState. I have read that React will call render any time this.setState is called. So in my mind, once the fetch() request comes back, it calls this.setState and changes the state to the returned data. This in turn should cause a re-render and the new state data should be available. I would be lying if I didn't say I was confused here. I was assuming that once the fetch() returned, it would update the state with the requested data, and then that would trigger a re-render.
There has to be something obvious that I am missing here, but my inexperience leaves me alone.. cold.. in the dark throws of despair. I don't have an issue working with 'hard coded' data, as I can pass that around just fine without worry of when it returns. For example, if I set the state in App.js to this.state = { currentPrice: [254.55] }, then I can access it in PriceOverview.js via this.props.data with zero issue. It's the async API request that is getting me here, and I am afraid it has gotten the best of me tonight.
Here App.js in full:
import React, { Component } from 'react';
import './components/css/App.css';
import NavigationBar from './components/NavigationBar';
import PriceOverview from './components/PriceOverview';
class App extends Component {
constructor() {
super();
this.state = {
currentPrice: null,
};
}
componentDidMount() {
const getCurrentPrice = () => {
const url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
fetch(url).then(data => data.json())
.then(currentPrice => {
this.setState = ({
currentPrice: currentPrice.bpi.USD.rate
})
console.log('API CALL', currentPrice.bpi);
}).catch((error) => {
console.log(error);
})
}
getCurrentPrice();
}
render() {
return (
<div>
<NavigationBar />
<PriceOverview data={this.state.currentPrice}/>
</div>
);
}
}
export default App;
Here is PriceOverview.js in full:
import React, { Component } from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
class PriceOverview extends Component {
constructor(props) {
super(props);
this.state = {
currentPrice: this.props.data
}
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.state.currentPrice != null ? <div className="price">{this.state.currentPrice}</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
export default PriceOverview;
Thank you in advance to any help, it's much appreciated.
this.setState ({
currentPrice: currentPrice.bpi.USD.rate
})
Do not put an = in this.setState
Ok First thing, when you're writting code on React the components that hold state are the class base components so ... What I see here is that you're creating two class base components so when you pass down props from your app class component to your PriceOverview wich is another class base component you're essentially doing nothing... Because when your constructor on your PriceOverview get call you're creating a new state on that Component and the previous state ( that's is the one you want to pass down) is being overwritten and that's why you're seem null when you want to display it. So it should work if you just change your PriveOverview component to a function base component ( or a dumb component). So this way when you pass down the state via props, you're displaying the correct state inside of your div. This is how would look like.
import React from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
const PriceOverview = (data) => {
return (
<div className="overviewBar">
<div className="currentPrice panel">
//Im calling data here because that's the name you gave it as ref
//No need to use 'this.props' you only use that to pass down props
{data != null ? <div className="price">
{data}</div> : <div className="price">Loading...</div>
}
</div>
</div>
)
}
}
export default PriceOverview;
Whenever you're writing new components start always with function base components if you component is just returning markup in it and you need to pass some data go to his parent component update it (making the api calls there or setting the state there) and pass down the props you want to render via ref. Read the React docs as much as you can, hope this explanation was useful (my apologies in advance if you don't understand quite well 'cause of my grammar I've to work on that)
The thing is constructor of any JS class is called only once. It is the render method that is called whenever you call this.setState.
So basically you are setting currentPrice to null for once and all in constructor and then accessing it using state so it will always be null.
Better approch would be using props.
You can do something like this in your PriceOverview.js.
import React, { Component } from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
class PriceOverview extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.props.data!= null ? <div className="price">{this.props.data}</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
export default PriceOverview;
Or you can use react lifecycle method componentWillReceiveProps to update the state of PriceOverview.js
componentWillReceiveProps(nextProps) {
this.setState({
currentPrice:nextProps.data
});
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.state.currentPrice != null ? <div className="price">{this.state.currentPrice }</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}

Categories