Issue displaying data when passing props in React - javascript
I'm trying to understand why this is throwing an error of name is undefined, but if I do it a different way, I can get data to display... Been stuck on this for quite awhile now and can't pass data to other components. This is the closest I've come, but don't know why one way works but the other says undefined. Trying to select and pass data from JobsTableApi.js to Title.js
JobsTableApi.js:
import React, { Component } from 'react'
//import Title from './components/header/Title.js'
let headers = {
'QB-Realm-Hostname': 'XXXXXXXXXXXXXX.quickbase.com',
'User-Agent': 'FileService_Integration_V2.1',
'Authorization': 'QB-USER-TOKEN XXXXX_XXXX_XXXXXXXXXXXXXX',
'Content-Type': 'application/json'
};
class JobsTableApi extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
};
}
componentDidMount() {
this.fetchData();
}
fetchData = () => {
let body = {"from":"bpz99ram7","select":[3,6,80,81,82,83,86,84,88,89,90,91,92,93,94,95,96,97,98,99,101,103,104,105,106,107,109,111,113,115,120,123,224,225,226,227,228,229,230,231,477,479,480,481],"where": "{40.CT. 'In Progress'}","sortBy":[{"fieldId":6,"order":"ASC"}],"groupBy":[{"fieldId":40,"grouping":"equal-values"}],"options":{"skip":0,"top":0,"compareWithAppLocalTime":false}}
fetch('https://api.quickbase.com/v1/records/query', {
method: 'POST',
headers: headers,
body: JSON.stringify(body)
}).then(response => response.json())
.then( data => this.setState({ data })
);
}
render() {
const { data } = this.state;
if (data === null) return 'Loading Job Data... ';
return (
<div>
{Object.keys(data["data"]).map(item => (
<div key = {item}>
{data["data"][item][6].value}
</div>
))}
</div>
)
}
}
export default JobsTableApi;
Title.js:
import { React } from 'react';
import PropTypes from 'prop-types';
import JobsTableApi from '../../JobsTableApi';
export default function Title() {
return(
<div>
<h3>
<JobsTableApi />
</h3>
</div>
)
}
Title.propTypes = {
name: PropTypes.string.isRequired
}
The above methods display all data coming over from my api call that has the field ID of 6 accurately, but since i'm attempting to pull different fields over to different components, I need to set it as props, but when I do, I get errors and says undefined. Example below.
JobsTableApi.js:
import React, { Component } from 'react'
import Title from './components/header/Title.js'
let headers = {
'QB-Realm-Hostname': 'XXXXXXXXXX.quickbase.com',
'User-Agent': 'FileService_Integration_V2.1',
'Authorization': 'QB-USER-TOKEN XXXXXXX_XXXX_XXXXXXXXXXXXXX',
'Content-Type': 'application/json'
};
class JobsTableApi extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
};
}
componentDidMount() {
this.fetchData();
}
fetchData = () => {
let body = {"from":"bpz99ram7","select":[3,6,80,81,82,83,86,84,88,89,90,91,92,93,94,95,96,97,98,99,101,103,104,105,106,107,109,111,113,115,120,123,224,225,226,227,228,229,230,231,477,479,480,481],"where": "{40.CT. 'In Progress'}","sortBy":[{"fieldId":6,"order":"ASC"}],"groupBy":[{"fieldId":40,"grouping":"equal-values"}],"options":{"skip":0,"top":0,"compareWithAppLocalTime":false}}
fetch('https://api.quickbase.com/v1/records/query', {
method: 'POST',
headers: headers,
body: JSON.stringify(body)
}).then(response => response.json())
.then( data => this.setState({ data })
);
}
render() {
const { data } = this.state;
if (data === null) return 'Loading Job Data... ';
return (
<div>
{Object.keys(data["data"]).map(item => (
<div key = {item}>
<Title name = {data["data"][item][6].value} />
</div>
))}
</div>
)
}
}
export default JobsTableApi;
Title.js:
import { React } from 'react';
import PropTypes from 'prop-types';
//import JobsTableApi from '../../JobsTableApi';
export default function Title({ name }) {
return(
<div>
<h3>
{name}
</h3>
</div>
)
}
Title.propTypes = {
name: PropTypes.string.isRequired
}
As you can see from my api call in the 'body' I'm pulling over many fields with values. From here I need to send these throughout my app into different components to be used.
Right now I have 2 api call files in my src folder, then src>components>charts>MultipleLineCharts.js files. As well as src>components>header>Title.js
I'm wondering if I need to change my structure and make the Api Calls on a parent component? Instead of this as siblings? App.js in src is rendering all. Any advice or guidance on this would be appreciated as well.
Thanks!
If the components you need to send the data to are all direct children of this parent API fetching component, then in the interests of starting simple, you don't need to use anything like React Context or Redux - keep it simple and get it working, then iterate as required.
The moment you build out a complex component hierarchy and unrelated or deeply-nested components need access to the data, then you probably want to look at something like Context / Redux.
`Right now I have 2 api call files in my src folder, then src>components>charts>MultipleLineCharts.js files. As well as src>components>header>Title.js
I'm wondering if I need to change my structure and make the Api Calls on a parent component? Instead of this as siblings?
`
You can use React Context or Redux for state management, So you can send or use data to any component.
Related
how to fetch url that contains an array dynamically
im having trouble figuring out how to fetch a url that contains an array in react the parent component fetches data that gets sent to two components. export default class ParentComponent extends Component<AuthProps, ChannelState> { constructor(props: AuthProps) { super(props) this.state = { ... } } getChannel = () => { console.log("get channel called") fetch(`${APIURL}/channel/mine`, { method: "GET", headers: new Headers({ "Content-Type": "application/json", "Authorization": `${this.props.sessionToken}` }) }) .then(response => response.json()) .then(data => { console.log(data) this.setState({ channel: data }) console.log(this.state.channel, "channel called") }) .catch(err => console.log(err)) } the state gets sent to two child components. childcomponent1 is a route that uses channelId in the fetch method. childcomponent2 displays a dynamic link to component1 using channelId as a key export default class ChildComponent1 extends Component<AuthProps, ChannelEntryState> { constructor(props: AuthProps) { super(props) this.state = { ... } } getChannelEntry = () => { console.log("get channel entry called") console.log(this.props.channel.length) fetch(`${APIURL}/channel/${this.props.channel[1].channelId}/channelentry`, { method: "GET", headers: new Headers({ "Content-Type": "application/json", "Authorization": `${this.props.sessionToken}` }) }) .then(response => response.json()) .then(data => { console.log(data) this.setState({ channelEntry: data.messages }) console.log(this.state.channelEntry, "channel entry called") }) .catch(err => console.log(err)) } const ChildComponent2 = (props: AuthProps) => { return( <Row> {props.channel.map((cprops: ChannelType) => { return( <> <Col> <div> <ul className="sidebar-list list-unstyled" key={cprops.channelId}> <li><Link to={`/channelEntry/${cprops.channelId}`}><Button onClick={() => {console.log('button clicked')}}>{cprops.name}</Button></Link></li> </ul> </div> </Col> </> ) })} Ive looked into useParams but i believe its only possible in a functional component. I believe i shouldnt use functional components when states can change. How can i fetch the url in component1 dynamically.
Concerning params you can access them in a react class component using this.props.match.params. And concerning the useParams, two things yes, anything with a use in front of a name should be a hook and can only be used in functional components. no, functional components, since React v17, can have their own states, using the useState hook. just keep in mind that the you can have multiple states in functional components so you should use a state for each controlled part.
for people using react typescript class components look into this link React-router-v6 access a url parameter
How to get an RTK Query API endpoint state (isLoading, error, etc) in a React Class component?
Ok, I think I've gone through almost all of RTK Query's docs, and read up on RTK Query's caching. Seems like it's a pretty big part of it, even if its not something I need at the moment. So, I'm trying to do a simple query using RKT Query in a class-based component, and then select from the Redux Store the isLoading state for the endpoint call. However, currently in the render() {} of my LoginPage.jsx, the endpoint.<name>.select()(state) call on mapStateToProps within LoginPageContainer.jsx doesn't seem to be working. (See code below). From looking at the examples from the docs on using RTK Query on classes, it looks like I'm missing a "cache key" in the .select(<cache_key>)(state) call. However, I haven't incorporated tags in my endpoint yet (I believe I don't have a need for them yet). My question: Can someone shed some light on what's the proper use on RTK Query generated endpoint's select() method used outside of React Hooks? I understand the idea behind cache tags for automatic re-fetching (but that's not likely what's wrong here), but I'm not sure how or what cache key I'm missing here to just get the running endpoint query state in a class component. Thanks, everyone! The Code: // LoginPage.jsx import React, { Component } from 'react' import PT from 'prop-types' import LoginForm from './components/LoginForm' export default class LoginPage extends Component { static propTypes = { loginWithUsername: PT.func.isRequired, loginWithUsernameState: PT.object.isRequired } render() { // This value never updates const { isLoading } = this.props.loginWithUsernameState // always outputs "{"status":"uninitialized","isUninitialized":true,"isLoading":false,"isSuccess":false,"isError":false}" // Even during and after running the `loginWithUsername` endpoint query console.log(this.props.loginWithUsernameState) return ( <div> {isLoading && 'Loading ...'} <LoginForm onSubmit={(values) => this.props.loginWithUsername(values)} /> </div> ) } } // LoginPageContainer.jsx import { connect } from 'react-redux' import { teacherApi } from './api' import LoginPage from './LoginPage' const { loginWithUsername } = teacherApi.endpoints const mapStateToProps = (state) => ({ loginWithUsernameState: loginWithUsername.select()(state) }) const mapDispatchToProps = (dispatch) => ({ loginWithUsername: (payload) => dispatch(loginWithUsername.initiate(payload)) }) export default connect(mapStateToProps, mapDispatchToProps)(LoginPage) // api.js import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react' export const teacherApi = createApi({ reducerPath: 'teacherApi', baseQuery: fetchBaseQuery({ baseUrl: '/teacher/' }), endpoints: (builder) => ({ loginWithUsername: builder.query({ query: (data) => ({ url: 'login', method: 'post', body: data, headers: { 'Content-Type': 'application/json' } }), }), }), })
The "cache key" passed to endpoint.select() is the same variable you're passing to your hook: useGetSomeItemQuery("a"); useGetSomeItemQuery("b"); const selectSomeItemA = endpoint.select("a"); const selectSomeItemB = endpoint.select("b"); const itemAREsults = selectSomeItemA(state); const itemBResults = selectSomeItemB(state); This results in looking up state => state[apiSlice.reducerPath].queries["getSomeItem('a')"], or whatever the exact cached data field is for that item.
const result = api.endpoints.getPosts.select()(state) const { data, status, error } = result Note that unlike the auto-generated query hooks, derived booleans such as isLoading, isFetching, isSuccess are not available here. The raw status enum is provided instead. https://redux-toolkit.js.org/rtk-query/usage/usage-without-react-hooks
localStorage.getItem ('token') returns null
In the Items component I set the toke in localstorage. The Details component tries to access this token. It gets null class Items extends Component { constructor (props) { super(props); } getT = () => { axios({ method: 'post', url: '/oauth2/token', data, config }) .then(res => { if (res.status === 200) { console.log(res.data) this.setState({ token: res.data }) localStorage.setItem('token', JSON.stringify(res.data['access_token'])) } else { const error = new Error(res.error); throw error; } }).catch(err => { console.error(err); alert('Error logging in please try again'); }); } render () { return ( <div> <ul className="instancesUl"> { this.props.items.map((item, index) => <li key={item.id} item={item} onClick = {this.getT} > </li> ) } </ul> <Details /> </div> ) } } class Details extends Component { constructor (props) { super(props); this.state = { } axios.defaults.headers.common['Authorization'] = localStorage.getItem('token'); } componentDidMount() { axios({ url: `https://app`, method: "GET" }) .then(res => { }) .catch(error => { }) } render () { return ( <div > </div> ) } }
The problem is that Details constructor is invoked earlier at the time when the localStorage is empty, after that your logic to set the token works but Details constructor does not execute again to get the updated value. Here is an example how you could tweak your code In short you should not rely on the state everywhere, init your state in a parent component and setState there, pass it properties to the children components via React props, so when you change the state your props will get updated also and force the component which they belong to render the update and call all needed logic.
You are reading before the write. Root of Problem: getT method of class Items is responsible for writing the localstorage and which has async call, It will take some time to get data from server so you don't know when it will return. Details component is rendered inside the Items component so it will be called immediately when it reaches the render method and as discussed in above point we are not sure token is written is localstorage or not. Solution: You can set some value in state saying token_is_saved You can render Details component only when token_is_saved is true Initialize state in Items constructor, this.state = { token: null } Modify your Items's render method as, { this.state.token && <Details /> }
Constructor of Details component works once in the very beginning while instantiating the component class and in that case there would be not token available in the localStorage. After if you click on the button, which later makes the call to the server and after promise is being resolved, you store the token. After refreshing your page, it will become available for you.
Is this the correct way of implementation? import Vue from 'vue'; import axios from 'axios'; const baseURL = "http://127.0.0.1:8000/api/v1"; const token = localStorage.getItem('token'); export default axios.create({ baseURL, headers: { 'Content-type' : 'application/json', 'Authorization' : 'Bearer ${token}' } });
how to fetch json data and render component (List) according to the response in ReactJS
I want to load book list (book files) from /list endpoint and list them in <ul>. I created a list component and then import it on index.jsx. But it doesn't work. How can I render component with json data fetched from server in body? list.component.jsx: import React from 'react' class List extends React.Component { constructor() { super() this.state = { files: [] } } componentDidMount() { let files = [] fetch('/books', { method: "POST", headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ dir: '/' }) }) .then(response => response.json()) .then(data => { this.setState({ files: data.books }) }) } render() { return ( <div> <h1>Book List</h1> <ul> {this.state.files.map(book => { return <li key={`book-${book.id}`}>{book.name}</li> })} </ul> </div> ) } } export default List index.jsx: import List from '../components/list' document.addEventListener('DOMContentLoaded', () => { ReactDOM.render( <List/>, document.body.appendChild(document.createElement('div')), ) }) I see that '/list' fetched in Network tab but no data on the browser. Errors given: list.jsx:31 Uncaught TypeError: this.state.files.map is not a function at List.render (list.jsx:31) list.jsx:31 Uncaught (in promise) TypeError: this.state.files.map is not a function at List.render (list.jsx:31)
Have you tried console.log statements directly before the setState command? Might be worth inspecting data and then data.books. Your data might actually be in data and not data.books. data.books might be a string and you may need to JSON.parse it to convert to an array.
Please follow the link https://codesandbox.io/s/2p5p0n4lpn Just check using ternary operator this.state.files is data available or not. If data is available then render. {this.state.files ? this.state.files.map(book => { return {book.userId}; }) : null}
React - Render HTML When Setting a State After Fetching Data
I have an application which needs to fetch invoice data from Stripe API (payment processor). When the invoice data has been returned, I'm trying to update my state using this.setState({invoiceData: invoices}) where invoices is a string of HTML that I build out from the data returned from the Stripe API. The issue is that the HTML isn't being rendered and is showing as plain text. I am pretty new to React and have only just got my head around rendering states, but now I'm pretty stuck on working this one out. What do I need to do to render the HTML? Please see my code below. import React from 'react'; class BillingInvoices extends React.Component { constructor(props) { super(props); this.state = { invoiceData: false } } // When the 'BillingInvoices' component is mounted: componentDidMount() { // Get invoice data from Stripe API. fetch('/stripe-invoices', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ customerId: '128973982' }) }) .then((response) => { if (response.ok) { return response.json(); } else { console.log('Error with Stripe response'); } }) .then((stripeData) => { var invoiceCount = stripeData['result']['data'].length; var i; var invoices = ''; for (i = 0; i < invoiceCount; i++) { invoices += '<div><a href="' + stripeData['result']['data'][i]['invoice_pdf'] + '" download>' + stripeData['result']['data'][i]['number'] + '</a></div>'; } this.setState({ invoiceData: invoices }) }) .catch((error) => { console.log('Error: ', error); }); } render() { return ( <div id="billing-invoices"> {this.state.invoiceData ? this.state.invoiceData : null} </div> ); } } export default BillingInvoices; Thank you for any insight.
I've stripped out some of your code for my example to make it easier to read: class BillingInvoices extends React.Component { constructor(props) { super(props); this.state = { invoiceData: [] } } componentDidMount() { fetch('/stripe-invoices') .then((response) => response.ok && response.json()) // Here I'm assigning the nested array to `invoiceData` immediately // so that you don't need to map over it later .then((data) => this.setState({ invoiceData: data.result.data })); } render() { // Here we can check if the data exists. If it doesn't // show a loading icon (or something) until it is if (!this.state.invoiceData) <Loader /> // ...otherwise show the data return ( <div id="billing-invoices"> // we map over the invoice data and for each invoice // return JSX (your div with an anchor populated with that invoice data) {this.state.invoiceData.map((invoice) => { return ( <div> <a href={invoice.invoice_pdf} download>{invoice.number}</a> </div> ) })} ); </div> ) } }
You can populate invoiceData with react components using JSX like so: let invoices = (<div>{stripeData['result']['data'].map(data => (<div><a href={data['invoice_pdf']} download>{data['number']}</a></div>))}</div>); this.setState({invoiceData: invoices}); You can replace the content of the second then clause with the above and leave the rest of the code unchanged.
Putting the resulted json in the component state is a good idea. But then, you should deal with this json directly in your render method, using the power of JSX. Check the official documentation about how to use JSX. This is a dummy example of what your component could look like with the usage of JSX: import React from "react"; class BillingInvoices extends React.Component { constructor(props) { super(props); } state = { invoices: [] } // When the 'BillingInvoices' component is mounted: componentDidMount() { // Get invoice data from Stripe API. fetch("/stripe-invoices", { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json" }, body: JSON.stringify({ customerId: "128973982" }) }) .then(response => { if (response.ok) { this.setState(invoices: response.json()); } else { console.log("Error with Stripe response"); } }) .catch(error => { console.log("Error: ", error); }); } render() { return ( <div id="billing-invoices"> {this.state.invoices.map((invoice, index) => { return ( <div key={index}>{invoice.name}</div> ) })} </div> ); } } export default BillingInvoices;