How to get state which comes from props which is got from API in React

78 views Asked by At

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.

enter image description here

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...

1

There are 1 answers

8
Drew Reese On

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>;
  }
}