React - Select specific data for Context Provider

765 Views Asked by At

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.

1

There are 1 best solutions below

2
On

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 ;