-1

Objective - Load same page layout from context provider with different data for each project.

I'm currently working on a Portfolio Website using React, React-Router, and React-Context. I need to be able to reuse the singleProject page layout but load different data depending on which card was selected.

Here's how the routing is currently handled. Each project card has it's own dynamic link based off it's title.

Single Project Card:

//ProjectSingle.js (Card)
import { motion } from 'framer-motion';
import { Link } from 'react-router-dom';

const ProjectSingle = ({ title, category, image }) => {
    return (
        <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1, delay: 1 }}
            transition={{
                ease: 'easeInOut',
                duration: 0.7,
                delay: 0.15,
            }}
        >
            <Link to={`/projects/${title}`} aria-label="Single Project">
                <div className="rounded-xl shadow-lg hover:shadow-xl cursor-pointer mb-10 sm:mb-0 bg-secondary-light dark:bg-ternary-dark">
                    <div>
                        <img
                            src={image}
                            className="rounded-t-xl border-none"
                            alt="Single Project"
                        />
                    </div>
                    <div className="text-center px-4 py-6">
                        <p className="font-general-medium text-lg md:text-xl text-ternary-dark dark:text-ternary-light mb-2">
                            {title}
                        </p>
                        <span className="text-lg text-ternary-dark dark:text-ternary-light">
                            {category}
                        </span>
                    </div>
                </div>
            </Link>
        </motion.div>
    );
};

export default ProjectSingle;

The router handles this and renders a singleProject (Page) component which then calls the singleProject (Page) context provider.

Routes:

function App() {
return (
    <AnimatePresence>
        <div className=" bg-secondary-light dark:bg-primary-dark transition duration-300">
            <Router>
                <ScrollToTop />
                <AppHeader />
                <Routes>
                    <Route path="/" element={<Home />} />
                    <Route path="projects" element={<Projects />} />
                    <Route
                        path='projects/:title'
                        element={<ProjectSingle />}
                    />

                    <Route path="about" element={<About />} />
                    <Route path="contact" element={<Contact />} />
                </Routes>
                <AppFooter />
            </Router>
            <UseScrollToTop />
        </div>
    </AnimatePresence>


);
}

export default App;

Single Project Page:

 const ProjectSingle = () => {

    const projTitle = useParams();

    return (
        <motion.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1, delay: 1 }}
            transition={{
                ease: 'easeInOut',
                duration: 0.6,
                delay: 0.15,
            }}
            className="container mx-auto mt-5 sm:mt-10"
        >
            <SingleProjectProvider data={projTitle} >
                <ProjectHeader />
                <ProjectGallery />
                <ProjectInfo />
                {/* <ProjectRelatedProjects /> */}
            </SingleProjectProvider>
        </motion.div>
    );
};

export default ProjectSingle;

Finally, the singleProjectProvider takes in the title which I'd planned to use to search for the specific singleProject data. Here's my current

Context provider for the singleProject (Page):

import { useState, createContext } from 'react';
import { singleProjectData as singleProjectDataJson } from '../data/singleProjectData';

const SingleProjectContext = createContext();

export const SingleProjectProvider = ({ children }, props) => {
    
    const title = props.data;

    const [singleProjectData, setSingleProjectData] = useState(
        singleProjectDataJson
    );

    
    return (
        <SingleProjectContext.Provider
            value={{ singleProjectData, setSingleProjectData }}
        >
            {children}
        </SingleProjectContext.Provider>
    );
};

export default SingleProjectContext;

Finally, this is the SingleProjectData.js where I need to select the correct data to use for the context provider provided a title. I've shortened it a lot for readers sake.

singleProjectData.js

export const singleProjectData = [
    {
        ProjectHeader: {
            title: 'React & Tailwindcss Portfolio',
            publishDate: 'Jul 26, 2021',
            tags: 'UI / Frontend',
        },
        
    },
    {
        ProjectHeader: {
            title: 'MechanALink',
            publishDate: 'Jul 26, 2021',
            tags: 'UI / Frontend',
        },
        
    },

    ];

Summary: After the title is passed into the context provider, I wish to then filter and select only the data that corresponds to the correct title. Then use this data to fill out the Context Provider's children.

Apologies for the lengthy question, there's a lot of routing and redirecting going on here.

  • Other than seeing a bug in the `SingleProjectProvider` component where it is trying to access a second undefined argument, what *exactly* is the question or issue here? `const SingleProjectProvider = ({ children }, props) => {` should be `const SingleProjectProvider = ({ children, ...props }) => {`. – Drew Reese May 25 '22 at 22:06

1 Answers1

0

Well:

  1. In Single Project Page retrieve the title from the params and pass it as prop to your context (projTitle is an object { title }):
const ProjectSingle = () => {
  const { title } = useParams();
  return  <SingleProjectProvider projectTitle={title} >{/*...*/}</SingleProjectProvider>
}
  1. In your context provider : the SingleProjectProvider it's a functional so the first argument is the props, ({ children }, props) should be corrected to ({ title, children }).
export const SingleProjectProvider = ({ children, title }) => {
    const [singleProjectData, setSingleProjectData] = useState(
        singleProjectDataJson.find(project => project.ProjectHeader.title)
    );
    // ..
}

I would use an id in place of a title with spaces and special characters (more resilient to URLs escaping).

In most cases: context should be avoided unless really necessary: it does seem to be a good use case, and bring more complexity that necessary ;

Gabriel Pichot
  • 2,325
  • 1
  • 22
  • 24
  • Doing this seemed to put things on the right track. However in my SingleProjectProvider the title prop comes up as undefined when I try to log it. It seems as if it's not being properly passed into the function call from the line: – StickOnAStick May 25 '22 at 22:48
  • @StickOnAStick Did you try logging the return value of `useParams()` ? – Gabriel Pichot May 26 '22 at 14:37