Home > Enterprise >  Why does the background color only change when the page is reloaded and not after a successful login
Why does the background color only change when the page is reloaded and not after a successful login

Time:01-08

When the user hits the login button, it redirects to the Unsplash login page. After a successful login, the page redirects back to "localhost" with the "code=" parameter in the URL (http://localhost:3000/?code=VbnuDo5fKJE16cjR#=). After that, I need to get the username of the current user and change the background color of his liked images.

Why does the background color only change when the page is reloaded and not after a successful login? There are too many requests happening at the same time and I don't know how to handle them properly.

Home.js

import React, { useState, useEffect } from "react";
import axios from "axios";
import ImageList from "../components/ImageList";
import SearchBar from "../components/SearchBar";
import Loader from "../helpers/Loader";
import Login from "../components/Login";

function Home() {
  const [page, setPage] = useState(1);
  const [query, setQuery] = useState("landscape");
  const [images, setImages] = useState([]);
  const [loading, setLoading] = useState(false);

  const clientId = process.env.REACT_APP_UNSPLASH_KEY;
  
  const url = `https://api.unsplash.com/search/photos?page=${page}&query=${query}&client_id=${clientId}&per_page=30`;

  const fetchImages = () => {
    setLoading(true);
    axios
      .get(url)
      .then((response) => {
        setImages([...images, ...response.data.results]);
      })
      .catch((error) => console.log(error))
      .finally(() => {
        setLoading(false);
      });
    setPage(page   1);
  };

  useEffect(() => {
    fetchImages();
    setQuery("");
  }, []);

  return (
    <div>
      <Login />
      {loading && <Loader />}
      <ImageList images={images} />
    </div>
  );
}

export default Home;

Login.js

import React, { useEffect } from "react"
import { useAppContext } from "../context/appContext";

function Login() {
  const { handleClick, getToken, token, getUserProfile } = useAppContext();

  
  useEffect(() => {
    if (window.location.search.includes("code=")) {
      getToken();
    }
    if (token) {
      getUserProfile();
    }
  }, [token]);

  return (
    <div>
      <button onClick={() => handleClick()}>Log in</button>
    </div>
  );
}

export default Login;

appContext.js

import React, { useReducer, useContext } from "react";
import reducer from "./reducer";
import axios from "axios";
import {SET_TOKEN,SET_LIKED_PHOTOS_ID } from "./actions";

const token = localStorage.getItem("token");
const username = localStorage.getItem("username");

const initialState = {
  token: token,
  username: username,
  likedPhotosId: [],
};

const AppContext = React.createContext();

const AppProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState);

  const handleClick = () => {
    window.location.href = `${api_auth_uri}?client_id=${client_id}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope.join(
      " "
    )}`;
  };

  const getToken = async () => {
    const urlCode = window.location.search.split("code=")[1];
    try {
    const { data } = await axios.post(
      `${api_token_uri}?client_id=${client_id}&client_secret=${client_secret}&redirect_uri=${redirect_uri}&code=${urlCode}&grant_type=${grant_type}`
    );
    const { access_token } = data;
    localStorage.setItem("token", access_token);
    dispatch({
      type: SET_TOKEN,
      payload: { access_token },
    });
    } catch (error) {
      console.log(error);
    }
  };

  const getUserProfile = async () => {
    try {
    const { data } = await axios.get(`https://api.unsplash.com/me`, {
      headers: {
        "Content-Type": "application/json",
        Authorization: "Bearer "   state.token,
      },
    });
    const { username } = data;
    localStorage.setItem("username", username);
    } catch (error) {
      console.log(error);
    }
  };

  const getLikedPhotos = async () => {
    try {
    const { data } = await axios.get(
      `https://api.unsplash.com/users/${state.username}/likes`,
      {
        headers: {
          "Content-Type": "application/json",
          Authorization: "Bearer "   state.token,
        },
      }
    );

    const likedPhotosId = data.map((photo) => photo.id);
    dispatch({
      type: SET_LIKED_PHOTOS_ID,
      payload: { likedPhotosId },
    });
    } catch (error) {
      console.log(error);
    }
  };

  return (
    <AppContext.Provider
      value={{
        ...state,
        handleClick,
        getToken,
        getUserProfile,
        getLikedPhotos,
      }}
    >
      {children}
    </AppContext.Provider>
  );
};

const useAppContext = () => useContext(AppContext);

export { AppProvider, initialState, useAppContext };

ImageList.js

import React, {useEffect } from "react";
import "../styles/ImageList.scss";
import { useAppContext } from "../context/appContext";

function ImageList({ images }) {
  const { username, likedPhotosId, getLikedPhotos } = useAppContext();

  useEffect(() => {
    if (username) {
      getLikedPhotos();
    }
  }, [username]);

  return (
    <div className="result">
          {images?.map((image) => (
            <div
              style={{
                backgroundColor: likedPhotosId?.includes(image.id) ? "red" : "",
              }}
            >
                <div key={image.id}>
                  <img src={image.urls.small} alt={image.alt_description} />
                </div>
            </div>
          ))}
    </div>
  );
}

export default ImageList;

reducer.js

import { SET_TOKEN, SET_LIKED_PHOTOS_ID } from "./actions";

const reducer = (state, action) => {
  if (action.type === SET_TOKEN) {
    return {
      ...state,
      token: action.payload.access_token,
    };
  }

  if (action.type === SET_LIKED_PHOTOS_ID) {
    return {
      ...state,
      likedPhotosId: action.payload.likedPhotosId,
    };
  }
  throw new Error(`no such action : ${action.type}`);
};

export default reducer;

CodePudding user response:

The problem is in your function. You save the username in localStorage but not in your reducer state:

const getUserProfile = async () => {
    try {
    const { data } = await axios.get(`https://api.unsplash.com/me`, {
      headers: {
        "Content-Type": "application/json",
        Authorization: "Bearer "   state.token,
      },
    });
    const { username } = data;
    localStorage.setItem("username", username);
    } catch (error) {
      console.log(error);
    }
  };

The issue here is that react doesn't trigger a rerender of components when you set something in localStorage and in your ImageList component use have a useEffect expecting username to change before calling the getLikedPhotos:

const { username, likedPhotosId, getLikedPhotos } = useAppContext();

useEffect(() => {
  if (username) {
    getLikedPhotos();
  }
}, [username]);

So to fix you need to add an action for setting the username state in your reducer:

import { SET_TOKEN, SET_LIKED_PHOTOS_ID, SET_USERNAME } from "./actions";

const reducer = (state, action) => {
  if (action.type === SET_TOKEN) {
    return {
      ...state,
      token: action.payload.access_token,
    };
  }

  if (action.type === SET_USERNAME) {
    return {
      ...state,
      username: action.payload.username,
    };
  }

  if (action.type === SET_LIKED_PHOTOS_ID) {
    return {
      ...state,
      likedPhotosId: action.payload.likedPhotosId,
    };
  }
  throw new Error(`no such action : ${action.type}`);
};

export default reducer;

And then dispatch that action from the getUserProfile:

const getUserProfile = async () => {
  try {
  const { data } = await axios.get(`https://api.unsplash.com/me`, {
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer "   state.token,
    },
  });
  const { username } = data;
  localStorage.setItem("username", username);
  dispatch({
    type: SET_USERNAME,
    payload: { username },
  });
  } catch (error) {
    console.log(error);
  }
};
  • Related