I'm trying to populate a table with some information pulled from my database through an API based on year. I'm using React Router and would like to keep my sidebar with the links to different years, but dynamically change the table that is the main focus of this page.
I can get the Table to render with the first year(2019, since this is the default link), but that's only if it's outside the <Switch>. This also causes the problem that it doesn't change when the link changes.
class YearlyTable extends React.Component {
state = {
yearlyTable: [],
isLoading: false,
}
componentDidMount() {
this.setState({ isLoading: true });
axios.get(
`http://localhost/yearTable/${this.props.match.params.yearId}${this.props.location.search}`,
{ withCredentials: true }
).then(res => {
const yearlyTable = res.data;
this.setState({ yearlyTable, isLoading: false });
}).catch((error) => {
console.log(error);
});
}
render() {
// isLoading component
// Check what API returns
console.log(this.state.yearlyBonds);
return (
// Removed for simplicity
// This returns a table
{this.state.yearlyTable && <ListTable title={this.state.yearlyTable.Title} data={this.state.yearlyTable.Bonds} />}
// This does not
<Switch>
<Route exact path={`/yearly_table/${this.props.match.params.yearId}${this.props.location.search}`} render={() => this.state.yearlyTable && <ListTable title={this.state.yearlyTable.Title} data={this.state.yearlyTable} />} />
</Switch>
// Sidebar removed for simplicity
);
}
}
export default withRouter(YearlyTable);
The outcome I'm wanting to achieve is that it renders the first table, but when you press one of the links, then it changes out the table with the new contents.
This is happening because you are using componentDidMount. This is called only for the first render, not after that.
You can do something like
class YearlyTable extends React.Component {
state = {
yearlyTable: [],
isLoading: false,
}
componentDidMount() {
this.setState({ isLoading: true });
axios.get(
`http://localhost/yearTable/${this.props.match.params.yearId}${this.props.location.search}`,
{ withCredentials: true }
).then(res => {
const yearlyTable = res.data;
this.setState({ yearlyTable, isLoading: false });
}).catch((error) => {
console.log(error);
});
}
updateData(){
axios.get(
`http://localhost/yearTable/newYearID${this.props.location.search}`,
{ withCredentials: true }
).then(res => {
const yearlyTable = res.data;
this.setState({ yearlyTable, isLoading: false });
}).catch((error) => {
console.log(error);
});
}
render() {
// isLoading component
// Check what API returns
console.log(this.state.yearlyBonds);
return (
// Removed for simplicity
// This returns a table
{this.state.yearlyTable && <ListTable title={this.state.yearlyTable.Title} data={this.state.yearlyTable.Bonds} />}
<Link onClick={this.updateData.bind(this, clickedItemYear)}>Some Item</Link>
);
}
}
export default withRouter(YearlyTable);
You can use and preventDefault or stopPropogation of the event. Better make a function call , so that it is called again whenever there is some user action.
Related
I'd like to pass data from App.js to another .js file within React. Atm, I'm reading and writing from local storage between files but this seems inefficient. I'd like to only pull from local storage once when the App.js component mounts. This is what I'm doing currently.
App.js:
constructor(props) {
super(props);
this.state = {
user: {},
user_data: (localStorage.getItem('user_data')),
}
this.authListener = this.authListener.bind(this);
}
componentDidMount() {
this.authListener();
}
//checks firebase for authentication
authListener() {
Authentication.auth().onAuthStateChanged((user) => {
console.log(user);
if (user) {
this.setState({ user });
localStorage.setItem('user', user.uid);
this.pulldata_Health();
this.pulldata_Meals();
this.pulldata_Ingredients();
} else {
this.setState({ user: null })
localStorage.removeItem('user');
localStorage.removeItem('user_data')
}
});
}
//connects to database and stores data to local storage
pulldata_Health() {
database.collection('Health_data')
.doc(localStorage.getItem('user'))
.get()
.then(doc => {
const data = doc.data();
localStorage.setItem('user_data', JSON.stringify(data));
console.log(JSON.parse(localStorage.getItem('user_data')))
}).catch(function (error) {
console.error("Error reading health", error);
});
Homepage.js:
constructor(props) {
super(props);
this.state = {
healthData: (JSON.parse(localStorage.getItem('user_data')))
}
}
componentDidMount() {
this.GoalChecker();
console.log(this.state.healthData);
}
GoalChecker() {
if (this.state.healthData !== null) {
if (this.state.healthData.goal === 'Gain') {
this.setState({ gainImage: true });
this.setState({ recompImage: false });
this.setState({ loseImage: false });
console.log('gainimg')
}
if (this.state.healthData.goal === 'Recomp') {
this.setState({ gainImage: false });
this.setState({ recompImage: true });
this.setState({ loseImage: false });
console.log('recompimg')
}
if (this.state.healthData.goal === 'Lose') {
this.setState({ gainImage: false });
this.setState({ recompImage: false });
this.setState({ loseImage: true });
console.log('loseimg')
}
}
};
This all works, but pulling from local storage every time this page loads seems a bit inefficient. Is there any way to push the props of User data from App.js to my other page?
It is very difficult for me to explain, but i'll show you a video from YouTube how to do this with react-hooks. It is not a very difficult method
https://youtu.be/XuFDcZABiDQ
YOu can use react context. Create a context within app.js/at the top of your application. Wrap the top-level container using a context component. Within any child components, you can then access props in the global context.
A great tutorial that explains it is here, https://kentcdodds.com/blog/how-to-use-react-context-effectively
I am trying to filter search my API data in react.js but I'm getting this error, cannot read property 'filter' of undefined. This is my JSON data link: https://alert-amigo-api.herokuapp.com/products/
Since the JSON data returns an array of objects, I have declared that in the props and used the same. What is the problem?
import FilterResults from 'react-filter-search';
import React, { Component } from "react";
class UserProfile extends Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
products: [],
value: ''
};
}
componentWillMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(
(result) => {
this.setState({
isLoaded: true,
products: result.products
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
console.log(this.state.products[0]);
}
handleChange = event => {
const { value } = event.target;
this.setState({ value });
};
render() {
const { error, isLoaded, products, value } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div className="content-margin">
<input type="text" value={value} onChange={this.handleChange} />
<FilterResults
value={value}
products={products}
renderResults={products => (
<div>
{products.map(el => (
<div>
<span>{el.productName}</span>
</div>
))}
</div>
)}
/>
</div>
);
}
}
}
export default UserProfile;
By testing that exact example, you will notice that the fetch URL does not return products:
fetch('https://jsonplaceholder.typicode.com/users')
The URL should be:
fetch('https://alert-amigo-api.herokuapp.com/products/')
Now result.products does contain an array. I suggest slightly bullet-proofing your code:
(result) => {
this.setState({
isLoaded: true,
products: result.products || [] // <-- add empty array
});
Or, if you prefer, on the render method:
render() {
{ products &&
<FilterResults ... />
}
}
I have a fetch request used on multiple pages, and would like to turn it into a component to simply call in whenever it's needed. This is proving to be harder than I thought, and it's bring up a number of issues.
I have tried using the wrappedComponent function but not sure if that's the solution as it's still not working. It's now saying that the fetchPosts class constructor cannot be invoked without new.
const that = this;
fetch ('/testrouter')
.then (response => {
return response.json();
}).then(jsonData => {
that.setState({posts:jsonData})
}).catch(err => {
console.log('Error fetch posts data '+err)
});
}
This is what I want to turn into a component, so that I can just call it by it's name from another one inside componentDidMount. I have tried doing this:
function fetchPosts(WrappedComponent) {
class FetchPosts extends Component {
constructor(props) {
super(props)
this.state = {
posts: []
}
}
fetchAllPosts() {
const that = this;
fetch ('/testrouter')
.then (response => {
return response.json();
}).then(jsonData => {
that.setState({posts:jsonData})
}).catch(err => {
console.log('Error fetch posts data '+err)
});
}
render() {
return (<WrappedComponent
fetchAllPosts = {this.fetchAllPosts})
/>);
}
}
return FetchPosts;
}
export default fetchPosts
Then importing it and calling it with fetchPosts but it's not working.
I was hoping I would be able to create a component, add the code then import the component, but this is not working.
You might want to create a custom hook to do this:
useFetch.jsx
import React, { useState, useEffect } from 'react'
const useFetch = (url) =>
const [state, setState] = useState({ loading: true, data: null, error: null })
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => setState(state => ({ ...state, loading: false, data }))
.catch(error => setState(state => ({ ...state, loading: false, error }))
},[])
return state
}
export default useFetch
MyComponent.jsx
import React from 'react'
import useFetch from './useFetch.jsx'
const MyComponent = () => {
const data = useFetch('/testrouter')
return (<>
{ data.loading && "Loading..." }
{ data.error && `There was an error during the fetch: {error.message}` }
{ data.data && <Posts posts={data.data}/> }
</>)
}
I am trying to load the data from exchangeratesapi but some how I cannot load the exchangerates's data!!
componentDidMount() {
this.setState({loading: true})
fetch("https://api.exchangeratesapi.io/latest")
.then(response => response.json())
.then(data => {
console.log(data)
this.setState({
loading: false,
currency: data,
})
})
}
render() {
var text = this.state.loading ? "loading..." : this.state.currency.BGN
return (
<div>
<p>{this.state.currency.RON}</p>
</div>
)
}
I have try on of the dumbest way to load the data.
omponentDidMount() {
this.setState({loading: true})
fetch("https://api.exchangeratesapi.io/latest")
.then(response => response.json())
.then(data => {
console.log(data)
this.setState({
loading: false,
currency: data,
bulgaria:data.rates.BGN,
})
})
}
And inside of render
var text = this.state.loading ? "loading..." : this.state.currency.bulgaria
But I believe there got a to be a better way to do this.
You are trying to access the property directly from currency however, it exists in rates.
This is incorrect:
{this.state.currency.RON}
It should be:
{this.state.currency.rates.RON}
Similarly, the variable text you created is not used anywhere. IMHO it should be like this:
render() {
const {loading, currency} = this.state;
console.log(currency); //only for debugging
return (
{ loading? 'Loading...' : (
<div>
<p>{currency.rates.RON}</p>
</div>)
}
)
}
In a react native project I am trying to set the user's profile photo as a tabBarIcon in tabNavigation. Below is how I am trying to retrieve the photo path and set it in the source for TabBarIcon.
First I have a token in AsyncStorage that gives me the username, email, or phonenumber of the user after login (works fine). This is in my constructor:
constructor(props) {
super(props)
this.state = {
Access: []
}
}
I set the Access in my state to a value in my AsyncStorage with getItem('Access') which i know works fine.
Now i have a function getProfilePhoto where I use fetch to get the profile photo.
getProfilePhoto = () => {
const { Access } = this.state.access;
fetch('http://urltofiletogetprofilephoto', {
method: 'POST',
headers: {
'Accept':'application/json',
'Content-Type':'application/json',
},
body: JSON.stringify({
Access:Access
})
}).then((response) => response.json())
.then((responseJson) => {
if(responseJson === 'NULL') {
console.log('../Images/NoPhoto.png');
} else {
console.log('../' + responseJson);
}
})
}
What I return from that file is:
$profilephoto = $row['ProfilePhoto'];
$profilephotoJson = json_encode($profilephoto);
echo $profilephotoJson;
That should return something like "Images/userprofilephoto.png". Now in navigationOptions I have this:
static navigationOptions = {
tabBarLabel: 'Profile',
tabBarIcon: ({ tintColor }) => (
<Image
source = {this.getProfilePhoto}
style={[styles.icon, {tintColor: tintColor}]}
/>
)
}
I thought calling the function would print the returned Image path, but when I run the app on my device I don't get an error but my tabBarIcon Image is just blank. I am new to react native and haven't worked with Json much I am hoping someone will be able to see something wrong that I am missing!
try
source={require(this.getProfilePhoto())}
However your function getProfilePhoto is not returning a path as you are using fetch.
Also navigationOptions is static, so this is not available.
You will need to access it via navigation params
static navigationOptions = ({ navigation }) => {
const { state } = navigation;
return {
tabBarLabel: 'Profile',
tabBarIcon: ({ tintColor }) => (
<Image
source = {state.params.getImage()}
style={[styles.icon, {tintColor: tintColor}]}
/>
)
}
}
componentWillMount() {
this.props.navigation.setParams({
getImage: () => {
this.getProfilePhoto();
},
});
}
getProfilePhoto () => {
//here you can get the path from this.props which would be added
//as before the component mounts
return this.props.profileImagePath; //set from redux connect
}
One downside with this is that if you want to update the image on the fly, you will need to call setParams again to force it to re-render the tab.
componentWillReceiveProps(nextProps) {
this.props.navigation.setParams({
getImage: () => {
this.getProfilePhoto();
},
});
}
I would have the action of getting the image separate to the component, and use Redux to connect to the latest image path. You can therefore set the Redux store triggered from another component.
You probably need to setState when your promise resolved by adding the data fetching request in the comoponentWillMount hook and make sure your image resides in the generated location relative to your component.
class UserProfile extends React.Component {
constructor(props) {
super(props)
this.state = {
Access: []
image: null
}
}
componentWillMount() {
this.getProfilePhoto();
}
getProfilePhoto = () => {
const { Access } = this.state.access;
fetch('http://urltofiletogetprofilephoto', {
method: 'POST',
headers: {
'Accept':'application/json',
'Content-Type':'application/json',
},
body: JSON.stringify({
Access:Access
})
}).then((response) => response.json())
.then((responseJson) => {
if(responseJson === 'NULL') {
console.log("../Images/NoPhoto.png");
} else {
this.setState({image: responseJson})
}
})
}
render() {
return (
this.state.image
?
<Image
source={require(this.state.image)}
style={this.props.style}
/>
:
null
)
}
}
static navigationOptions = {
tabBarLabel: 'Profile',
tabBarIcon: ({ tintColor }) => (
<UserProfile
style={[styles.icon, {tintColor: tintColor}]}
/>
)
}