I want to get data using fetch() and pass it down my component hierarchy, and use that data to set the initial state of one of my components
I have tried setting the inital state using the props and passing them down.
componentDidMount = () => {
getFileSystem().then(response => {
if (response.success) {
this.setState({
filesystem: response.filesystem,
projects: response.projects
})
}
}).catch(err => {
this.setState({
filesystem: {
name: '/',
type: 'directory',
children: [
{ name: 'error.txt', type: 'file', data: 'error' }
]
},
projects: []
})
})
}
class TerminalContainer extends Component {
constructor(props) {
super(props)
this.state = {
filesystem: props.filesystem,
terminal_data: [''],
current_dir_name: '/',
current_dir: props.filesystem,
full_path: ""
}
}
...
But the component calls the constructor function before the data is loaded into the props of the component. This means that the inital state of the component is not set properly.
I need some way of preventing the component from being rendered until all of the data is ready
If you want to use the props given to a component as initial state, and these props are state in a parent component that are fetched asynchronously, you need to delay the rendering of the child component.
You could e.g. add an additional piece of state called isLoading that you set to false when the fetch is complete and use that to conditionally render the TerminalContainer component.
Example
class App extends React.Component {
state = {
isLoading: true,
filesystem: null,
projects: null
};
componentDidMount() {
getFileSystem()
.then(response => {
if (response.success) {
this.setState({
isLoading: false,
filesystem: response.filesystem,
projects: response.projects
});
}
})
.catch(err => {
this.setState({
isLoading: false,
filesystem: {
name: "/",
type: "directory",
children: [{ name: "error.txt", type: "file", data: "error" }]
},
projects: []
});
});
}
render() {
const { isLoading, filesystem } = this.state;
if (isLoading) {
return null;
}
return <TerminalContainer filesystem={filesystem} />;
}
}
Related
What I want to do
When a child component first rendering, I would like to use the value in props from a parent component
Problem
When a child component is first rendered, props is not set to state in the child component
I am a beginner to React. I am trying to use props in order to call API by axios in componentDidMount in a child component. I mean, what I am doing is calling API in a parent component and setting data from this API to a child component as props.
However, when I try to do that, props is not set to state in a child component.
For example, when I retrieve product which has some category, I type localhost:3000/api/parent/?category=1/. But, my console.log shows me localhost:3000/api/parent/?category=undefined because I guess props is not set when a child component first rendering.
Actually, I can see category object in state like below.
I guess props is completely set to state after the child component finish first rendering.
How could I set props which comes from API to state?
Although I tried many solutions I found on the stackoverflow, I got stuck at this problem so long time.
I would like you to tell me some solutions.
Thank you very much.
== == ==
My code is like this.
Parent Component
class Top extends Component {
constructor(props) {
super(props);
this.state = {
loginUser: '',
categories: [],
};
}
async componentDidMount() {
const localhostUrl = 'http://localhost:8000/api/';
const topCategoryList = ['Smartphone', 'Tablet', 'Laptop'];
let passCategoryToState=[]
axios
.get(localhostUrl + 'user/' + localStorage.getItem('uid'))
.then((res) => {
this.setState({ loginUser: res.data });
})
.catch((err) => console.log(err));
await Promise.all(
topCategoryList.map(async (category) => {
await axios.get(localhostUrl + 'category/?name=' + category).then((res) => {
passCategoryToState=[...passCategoryToState, res.data]
console.log(passCategoryToState);
});
})
);
this.setState({categories : passCategoryToState})
}
render() {
if (!this.props.isAuthenticated) {
return <p> Developing now </p>;
}
if (this.state.loginUser === '' ) {
return <CircularProgress />;
} else {
return (
<>
<Header loginUser={this.state.loginUser} />
<Give_Item_List
axiosUrl="http://localhost:8000/api/"
subtitle="Smartphone Items"
loginUser={this.state.loginUser}
category={this.state.categories[0]}
/>
</>
);
}
}
}
export default Top;
And, child component
class Give_Item_List extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
loginUser: this.props.loginUser,
category: this.props.category,
};
}
async componentDidMount() {
let pickedGiveItems;
await this.setState({ loading: true });
await axios
.get(this.props.axiosUrl + 'giveitem/?category=' + this.state.category.id)
.then((res) => {
pickedGiveItems = res.data;
console.log(pickedGiveItems);
})
.catch((err) => console.log('Not found related to Items'));
this.setState({ loading: false });
}
render() {
if (this.state.loading == true) {
return <CircularProgress />;
}
return <h1>Give_Item_List</h1>;
}
}
export default Give_Item_List;
==============
Edit:Change to componentDidUpdate
componentDidUpdate(prevProps) {
if(prevProps.category != this.props.category){
let pickedGiveItems;
this.setState({ loading: true });
axios
.get(this.props.axiosUrl + 'giveitem/?category=' + this.props.category.id)
.then((res) => {
pickedGiveItems = res.data;
console.log(pickedGiveItems);
})
.catch((err) => console.log('NotFount'));
this.setState({ loading: false });
}
}
It still doesn't work...
In the constructor, this.props is undefined, but you can access the props directly.
constructor(props) {
super(props);
this.state = {
loading: false,
loginUser: props.loginUser,
category: props.category,
};
}
However, I should note now that storing props in state is a common react anti-pattern, you should always consume prop values from this.props where you need them.
For example:
async componentDidMount() {
let pickedGiveItems;
await this.setState({ loading: true });
await axios
.get(this.props.axiosUrl + 'giveitem/?category=' + this.props.category.id)
.then((res) => {
pickedGiveItems = res.data;
console.log(pickedGiveItems);
})
.catch((err) => console.log('Not found related to Items'));
this.setState({ loading: false });
}
You also can't await a react state update since it isn't an async function nor does it return a Promise. Just provide an initial true loading state and toggle false when the fetch request resolves.
class Give_Item_List extends Component {
constructor(props) {
super(props);
this.state = {
loading: true
};
}
async componentDidMount() {
let pickedGiveItems;
await axios
.get(this.props.axiosUrl + 'giveitem/?category=' + this.props.category.id)
.then((res) => {
pickedGiveItems = res.data;
console.log(pickedGiveItems);
})
.catch((err) => console.log('Not found related to Items'));
this.setState({ loading: false });
}
render() {
if (this.state.loading) {
return <CircularProgress />;
}
return <h1>Give_Item_List</h1>;
}
}
I'm making Log In Stuff for my application.All I want to is to update "username" in TodoApp.jsx component and set it to the same value as Login.jsx "Username"
Any suggestions?
TodoApp.jsx
class ToDoApp extends Component {
state = {
username:'',
inputValue: '',
todos: [],
currentPage: 1,
pageCount: 1,
itemsPerPage: 10,
};
This function creates new item and adds it to the todos:[] in state
addItem = () => {
let {todos} = this.state
if (this.inpRef.current.value === '') {
return alert('We dont do that here....')
} else {
axios
.post(`http://localhost:8080/add`, {
todo: this.inpRef.current.value,
checked: false,
})
.then((res) => {
this.setState({
todos:[...todos,{todo:res.data.todo,_id:res.data._id,checked:false}]
})
})
.catch((err) => {
console.log("err", err);
});
this.setPageCount()
}
this.inpRef.current.value = ''
console.log('--------this.state.todos', this.state.todos);
}
Here is Login.jsx code:
class Login extends Component {
state = {
username:'',
password:'',
}
This is a function which handles Log In.
handleLogIn = () => {
const { username, password } = this.state
if (password.length === 0 || username.length === 0 ) {
alert('Incorrect Login!')
} else {
axios
.post(`http://localhost:8080/logIn`, {
username: username,
password: password,
})
.then((res) => {
this.props.history.push("/todoapp");
console.log('res',res)
})
.catch((err) => {
console.log("err", err);
});
}
}
You can use the props property of React.js to achieve that.
If we assume that you've called your Login component in ToDoApp
(which makes ToDoApp the father and Login the child component); then what you can do is to define a method that sets the username state of the child component.
For example:
Login Component: Call the handle_username function of the father component once you set the username state.
this.setState({username: (username input)})
this.props.handle_username(this.state.username)
ToDoApp Component: Define the function handle_username and pass it as props to the Login Component.
handle_username = (username) => {
this.setState({username: username})
}
End pass it as props:
<Login handle_username={this.handle_username}/>
You can using Redux to achieve this.
passing props between this two components.
Calling API to update data and fetch API on another page.
Using browser localstorage to store the input data.
I'm creating a block for Wordpress with Gutenberg Editor, which is working on React js.
So I'm calling Wordpress API by apiFetch(), which is same to fetch():
class PortfolioTagsEdit extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: false,
};
}
componentDidMount() {
const { attributes } = this.props;
const { switcher } = attributes;
this.setState({ isLoading: true });
apiFetch( { path: `/wp/v2/${switcher}?post` } )
.then(data => this.setState({ data, isLoading: false }));
}
...
}
For variable switcher I have controllers which are changing the value.
My problem is when I switch the value of switcher I should reload api call, but I don't know how)
Can you help me, please?
Using react hooks you can use useEffect for fetching API.
function PortfolioTagsEdit({ attributes }) {
// state
const [loading, setLoading] = useState(false);
const [data, setData] = useState([])
// here useEffect will run on component mount and every-time attributes.switcher changes
useEffect(() => {
setLoading(true)
apiFetch( { path: `/wp/v2/${switcher}?post` } )
.then(data => {
setLoading(false)
setData(data)
});
}, [attributes.switcher])
return (
....
)
}
The easiest way to do this would be to have the switcher variable in state. You can then implement the componentDidUpdate method to call your apiFetch:
class PortfolioTagsEdit extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: false,
switcher: this.props.attributes.switcher
};
}
componentDidMount() {
this.callAPI()
}
componentDidUpdate(prevProps, prevState) {
if (prevState.switcher !== this.state.switcher) this.callAPI();
}
callAPI() {
const { switcher } = this.state;
this.setState({ isLoading: true });
apiFetch( { path: `/wp/v2/${switcher}?post` } )
.then(data => this.setState({ data, isLoading: false }));
}
...
}
Check out the docs for componentDidUpdate - https://reactjs.org/docs/react-component.html#componentdidupdate
You could also take a look on how to do this using hooks, specifically useEffect -https://reactjs.org/docs/hooks-reference.html#useeffect
I want to pass my data array and favorites array which both I am setting in states. I want to pass my favorites array state in my data state. How can I achieve that?? My code looks like this
class Favorites extends Component {
constructor(props) {
super(props);
this.state = {
favorites: [],
data: [],
};
}
axios
.post(
'http://staging.islamicmedia.com.au/wp-json/islamic-media/v1/user/media/library',
data,
)
.then((res) => {
console.log(res.data);
this.setState({
data: res.data,
favorites: res.data.data.favorite.filter((val) => val != null),
});
});
};
You should do that axios call in the componentDidMount:
class Favorites extends Component {
constructor(props) {
super(props);
this.state = {
favorites: [],
data: [],
};
}
componentDidMount() {
axios
.post(
'http://staging.islamicmedia.com.au/wp-json/islamic-media/v1/user/media/library',
data,
)
.then((res) => {
const favs = res.data.data.favorite.filter((val) => val !== null);
this.setState({
data: res.data,
favorites: favs
});
});
};
}
My parent component shows an error about my props:
Warning: Failed prop type: The prop prop name here is marked as required in ParentComponent, but its value is undefined.
The values are not undefined though.
When I remove isRequired from my PropTypes I get the error:
'Type Error: this.state is undefined'
But again my state is defined above.
I am new to react and javascript in general, any help is appreciated :)
I thought maybe it was because I was using arrow functions, so i switched to regular functions and bind them in the constructor but that didn't work. The code worked when it wasn't separated into components like it is now. What am I doing wrong here guys?
import PropTypes from 'prop-types';
import apiKey from '../config/apiKey'
import SearchForm from './SearchForm'
import SearchResults from './SearchResults'
import Details from './Details'
class ParentComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
term: '',
location: '',
isLoading: false,
businesses: [],
business: {}
}
}
handleChange = (event) => {
const { name, value } = event.target;
this.setState({
[name]: value
})
}
// Get data based on search term and location
handleSubmit = (event) => {
event.preventDefault();
const term = this.state.term;
const location = this.state.location;
this.setState({
isLoading: true
});
fetch(`https://cors-anywhere.herokuapp.com/https://api.yelp.com/v3/businesses/search?term=${term}&location=${location}`,
{
headers: { 'Authorization': `Bearer ${apiKey}` }
})
.then(response => response.json())
.then(response => {
const Businesses = response.businesses.map(business => {
return {
id: business.id,
imageSrc: business.image_url,
name: business.name,
address: business.location.address1,
city: business.location.city,
state: business.location.state,
zipCode: business.location.zip_code,
category: business.categories.title,
rating: business.rating,
reviewCount: business.review_count
}
});
return this.setState({
businesses: Businesses,
isLoading: false
});
})
.catch(err => console.log(err))
};
BusinessList = this.state.businesses.map(business => {
return {
id: business.id,
imageSrc: business.image_url,
name: business.name,
address: business.location.address1,
city: business.location.city,
state: business.location.state,
zipCode: business.location.zip_code,
category: business.categories.title,
rating: business.rating,
reviewCount: business.review_count
}
})
// Get detail data from business based on id extracted from previous fetch request
getDetails = () => {
return fetch(`https://cors-anywhere.herokuapp.com/https://api.yelp.com/v3/businesses/${this.BusinessList.id}`,
{
headers: { 'Authorization': `Bearer ${apiKey}` }
})
.then(response => response.json())
.then(response => {
const data = {
id: response.id,
alias: response.alias,
name: response.name
}
this.setState({
business: data
});
})
.catch(err => console.log(err))
};
render() {
return (
<div>
<SearchForm
handleSubmit={this.handleSubmit}
handleChange={this.handleChange}
term={this.state.term}
location={this.state.location}
/>
<SearchResults
key={this.BusinessList.id}
id={this.BusinessList.id}
imageSrc={this.BusinessList.imageSrc}
name={this.BusinessList.name}
address={this.BusinessList.address}
city={this.BusinessList.city}
state={this.BusinessList.city}
zipCode={this.BusinessList.zipCode}
category={this.BusinessList.category}
rating={this.BusinessList.rating}
reviewCount={this.BusinessList.reviewCount}
/>
<Details
key={this.business.id}
id={this.business.id}
alias={this.business.alias}
name={this.business.name}
/>
</div>
);
};
};
ParentComponent.propTypes = {
term: PropTypes.string.isRequired,
location: PropTypes.string.isRequired,
isLoading: PropTypes.bool.isRequired,
businesses: PropTypes.array.isRequired,
business: PropTypes.object.isRequired
}
export default ParentComponent;
PropTypes are used for props that are passed down to the component and not for the state variables in the component.
ParentComponent.propTypes = {
term: PropTypes.string.isRequired,
location: PropTypes.string.isRequired,
isLoading: PropTypes.bool.isRequired,
businesses: PropTypes.array.isRequired,
business: PropTypes.object.isRequired
}
When you do this, React thinks that term, location, isLoading, businesses and business are passed as props to the ParentComponent. But I see that they are all just state variables.
To understand better, take this case:
<Details
key={this.business.id}
id={this.business.id}
alias={this.business.alias}
name={this.business.name}
/>
You can add key, id, alias, name as PropTypes for Details component.