Search code examples
reactjsapiaxiosuse-effectuse-state

How to set the data from my API as initial state for my react component?


I am making the profile page for my website, but when I initially render components the data from the API somehow doesn't load up in the initial 'state' I made for About, Blog or Stats.

enter image description here

However when I click on About again the the API data is rendered.

enter image description here

IDK if I am able to make my point, but this is how my react functional component looks like:

const Profile = () => {
    let history = useHistory();
    const [user, setUser] = useState({});
    useEffect(() => {
        async function fetchMyAPI() {
           try {
               let res = await api.get('user/isLoggedIn', { withCredentials: true });
               if (res.data.user) {
                   setUser(res.data.user);
               }
           }catch (err) {
                history.push('/login');
           }
       }
       fetchMyAPI();
    }, []);

    const About = ({ user }) => {
        if (user)
            return (
                <div className="about_wrapper">
                    <p className="name_about">{`${user.name}`}</p>
                    <p className="email_about">{`${user.email}`}</p>
                    <p><FontAwesomeIcon icon={faPen} />{`  ${user.niche}`}</p>
                    <p><FontAwesomeIcon icon={faAlignLeft} />{` ${user.job}`}</p>
                    <p className="description_about"><FontAwesomeIcon icon={faAddressBook} />{` ${user.description}`}</p>
                </div>
            );
        else
            return (
                <h3>Loading..</h3>
            );
}
    const Blogs = (props) => {
        return (
            <>
            </>
        );
    }
    const Stats = (props) => {
        return (
            <>
            </>
        );
    }

    const [state, stateSetter] = useState(<About user={user}/>);

    const clickHandler = (e) => {
        e.preventDefault()
        const nav = document.querySelectorAll('.info_links');
        switch (e.target.textContent) {
            case 'About':
                nav[1].classList.remove('un-p');
                nav[2].classList.remove('un-p');
                stateSetter(<About user={user}/>);
                break;
            case 'Stats':
                nav[0].classList.remove('un-p');
                nav[1].classList.remove('un-p');
                stateSetter(<Stats user={user}/>);
                break;
            case 'Blogs':
                nav[0].classList.remove('un-p');
                nav[2].classList.remove('un-p');
                stateSetter(<Blogs user={user}/>);
                break;
            default:
             console.log('error')
        }
        e.target.classList.add('un-p');
    }

    return (
        <>
            <Navbar />
            <div>
                <div className="CoverImage FlexEmbed FlexEmbed--2by1" style={{ backgroundImage: `url(${user.cover}})` }}></div>
                <img className="avatar" src={`${user.dp}`} alt="Girl in a jacket" />
                <div className="info">
                    <p className="info_links link-grow un un-p" onClick={clickHandler}>About</p>
                    <p className="info_links un" onClick={clickHandler}>Blogs</p>
                    <p className="info_links un" onClick={clickHandler}>Stats</p>
                </div>
                {state}
            </div>
        </>
    );
}

export default Profile

I want to set the data from my API as initial state for my react component!


Solution

  • Few issues:

    1. You are providing a truthy initial value for the user state so the conditional rendering isn't working as you are probably expecting. I.E. since user is ({}) the if (user) check is true and you attempt to render the undefined properties.
    2. You are storing JSX in component state, an anti-pattern in React.
    3. You are declaring React components in the body of a function component, this will have the effect of creating new components each render cycle and unmount & remount the components. This can have adverse effects on performance.

    Move About, Blogs, and Stats out of Profile component so they have stable references.

    const About = ({ user }) => {
      if (user)
        return (
          <div className="about_wrapper">
            <p className="name_about">{`${user.name}`}</p>
            <p className="email_about">{`${user.email}`}</p>
            <p><FontAwesomeIcon icon={faPen} />{`${user.niche}`}</p>
            <p><FontAwesomeIcon icon={faAlignLeft} />{`${user.job}`}</p>
            <p className="description_about">
              <FontAwesomeIcon icon={faAddressBook} />
              {`${user.description}`}
            </p>
          </div>
        );
      else
        return (
          <h3>Loading..</h3>
        );
    };
    
    const Blogs = (props) => {
      return (
        <>
        </>
      );
    };
    
    const Stats = (props) => {
      return (
        <>
        </>
      );
    };
    

    Store the component "type" in state and conditionally render them, passing the user state to them.

    const Profile = () => {
      const history = useHistory();
      const [user, setUser] = useState(); // <-- undefined, falsey
      const [state, stateSetter] = useState('About'); // <-- use type
    
      useEffect(() => {
        async function fetchMyAPI() {
          try {
            let res = await api.get('user/isLoggedIn', { withCredentials: true });
            if (res.data.user) {
              setUser(res.data.user);
            }
          } catch(err) {
            history.push('/login');
          }
        }
        fetchMyAPI();
      }, []);
    
      const clickHandler = (e) => {
        e.preventDefault()
        const nav = document.querySelectorAll('.info_links');
        switch (e.target.textContent) {
          case 'About':
            nav[1].classList.remove('un-p');
            nav[2].classList.remove('un-p');
            stateSetter('About'); // <-- update state type
            break;
          case 'Stats':
            nav[0].classList.remove('un-p');
            nav[1].classList.remove('un-p');
            stateSetter('Stats'); // <-- update state type
            break;
          case 'Blogs':
            nav[0].classList.remove('un-p');
            nav[2].classList.remove('un-p');
            stateSetter('Blogs'); // <-- update state type
            break;
          default:
            console.log('error')
        }
        e.target.classList.add('un-p');
      }
    
      return (
        <>
          <Navbar />
          <div>
            <div
              className="CoverImage FlexEmbed FlexEmbed--2by1"
              style={{ backgroundImage: `url(${user.cover}})` }}>
            </div>
            <img className="avatar" src={`${user.dp}`} alt="Girl in a jacket" />
            <div className="info">
              <p className="info_links link-grow un un-p" onClick={clickHandler}>About</p>
              <p className="info_links un" onClick={clickHandler}>Blogs</p>
              <p className="info_links un" onClick={clickHandler}>Stats</p>
            </div>
            {state === 'About' && <About user={user} />}
            {state === 'Stats' && <Stats user={user} />}
            {state === 'Blogs' && <Blogs user={user} />}
          </div>
        </>
      );
    }