Linking URL parameter to context? (React portfolio site)

Viewed 42

I have a (very) small working portfolio site built using React that I want to use to feature my work as both a graphic designer and now a (fledgling) web developer. Visitors are greeted with a landing page of sorts, and their choice to view "design" or "web" work sets a global state as "design" or "web" in context.

Based on this choice, the subsequent page that visitors are taken to (About.js) will populate with a short bio and a set of either graphic design projects or web development projects. The issue is that I'd like the URL to match this decision: "/design" or "/web".

Here's what I'd like to fix. Say I choose the "design" option on the first page. This sets global state, and takes me to "/design". If I manually type and change the URL in "/web" then About.js repopulates accordingly with the updated short bio and featured projects. This does not change back if I type in "/design" again. The same happens if I start with "/web", etc.

How would I get the bio and featured projects rendered based on my global state to change accordingly with the URL? Or rather, why does this only happen when I manually switch URLs once? I know this may have to do with employing useEffect, but that was how I arrived at the correct URLs displaying in the first place.

App.js

import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";

import Landing from "./components/Landing";
import About from "./components/About";
import './App.css';
import SiteProvider from "./utils/SiteContext";

function App() {
  return (
    <div className="App">
      <SiteProvider>
        <Router>
          <Routes>
            <Route element={<Landing />} exact path="/" />
            <Route element={<About />} exact path="/design" />
            <Route element={<About />} exact path="/web" />
          </Routes>
        </Router>
      </SiteProvider>
    </div>
  );
}

export default App;

SiteContext.js

import React, { createContext, useState, useContext } from "react";

export const SiteContext = createContext();
export const useSite = () => useContext(SiteContext);

export default function SiteProvider({ children }) {
    const [field, setField] = useState('');

    const pickDesign = () => {
        return setField("design")
    };

    const pickWeb = () => {
        return setField("web")
    };
  
    return (
      <SiteContext.Provider value={{ field, pickDesign, pickWeb }}>
        {children}
      </SiteContext.Provider>
    );
  };

Landing.js

import React, { useEffect } from "react";
import { useSite } from "../utils/SiteContext";
import { useNavigate } from "react-router-dom";
import '../App.css';

export default function Landing() {
  const { field, pickDesign, pickWeb } = useSite();

  const navigate = useNavigate();

  useEffect(() => {
    navigate("/"+`${field}`);
  }, [field])

  return (
    <div>
      <section id="about">
        <div id="bio" onClick={pickDesign}>
            <h1>gz919 is a <span className="bioBold">graphic designer</span>.</h1>
        </div>

        <div id="bio2" onClick={pickWeb}>
            <h1>gz919 is a <span className="bioBold2">web developer</span>.</h1>
        </div>
      </section>
    </div>
  );
}

About.js

import React from "react";
import { useSite } from "../utils/SiteContext";
import '../App.css';

import Header from "./Header";
import Projects from "./Projects";
import Contact from "./Contact";
import Footer from "./Footer";

import { designProjects, webProjects } from "../utils/projectlists.js";

function About() {
  const { field } = useSite();

  return (
    <div>
      <Header />

      <div>
        <section id="about">
          <div id="bio">
            {field === "design" ?
              <h1>gz919 is a <span className="bioBold">graphic designer</span> from and based in San Antonio, Texas.</h1> :
              <h1>gz919 is a <span className="bioBold">web developer</span> from and based in San Antonio, Texas.</h1>
            }  
          </div>
        </section>
      </div>
      
      {field==="design" ? <Projects projects={designProjects}/> : <Projects projects={webProjects}/>}

      <Contact />
      <Footer />
    </div>
  );
}

export default About;
2 Answers

I think it might be easier if you use the react-router package. Instead of the useSite() hook with the context, just add const location = useLocation(); to your about component.

Then you can access the url with location.pathname and use that value in your ternary instead of the field from your context provider.

ETA: Source and Docs: https://v5.reactrouter.com/web/api/Hooks/uselocation

You are over complicating things a bit with the React context and state. You can simplify the entire process by passing a prop for which content you want to access/render in the About component.

App

Remove the SiteProvider component and pass a prop to About for which type of content it should access/render.

function App() {
  return (
    <div className="App">
      <Router>
        <Routes>
          <Route path="/" element={<Landing />} />
          <Route path="/design" element={<About field="design" />} />
          <Route path="/web" element={<About field="web />} />
        </Routes>
      </Router>
    </div>
  );
}

About

Destructure and access the field prop.

function About({ field }) {
  return (
    <div>
      <Header />

      <div>
        <section id="about">
          <div id="bio">
            {field === "design"
              ? (
                <h1>
                  gz919 is a <span className="bioBold">graphic designer</span> from and based in San Antonio, Texas.
                </h1>
              ) : (
                <h1>
                  gz919 is a <span className="bioBold">web developer</span> from and based in San Antonio, Texas.
                </h1>
              )
            }  
          </div>
        </section>
      </div>
      
      {field === "design"
        ? <Projects projects={designProjects} />
        : <Projects projects={webProjects} />
      }
      <Contact />
      <Footer />
    </div>
  );
}

Landing

Update the click handler to just navigate to the appropriate route path.

export default function Landing() {
  const navigate = useNavigate();

  const navigateTo = target => () => navigate(target);

  return (
    <div>
      <section id="about">
        <div id="bio" onClick={navigateTo("/design")}>
          <h1>gz919 is a <span className="bioBold">graphic designer</span>.</h1>
        </div>

        <div id="bio2" onClick={navigateTo("/web")}>
          <h1>gz919 is a <span className="bioBold2">web developer</span>.</h1>
        </div>
      </section>
    </div>
  );
}
Related