Search code examples
javascriptreactjsarraysreact-hooksreact-router-dom

Cannot pass data from one page to another using useState on button click


I am running a function when the user clicks a button, this function updates my useState and adds an array into it as data. This data later on is passed into another webpage where the array objects are displayed in a different way. I'm running into an issue where, my useState is not updating and instead returns an empty array. I suspect it has something to do with the new page refreshing and not updating the useState. I am just not sure where to go from here.

I am using React-Router V6 for this

Here is my Parent webpage along with the button.

<div className="toggleButtons" key={id}>
  <Link
    to={"/DetailsPage"}
    onClick={toggledItem}
    state={{ data: data }}
    className="primary-ff boxyButton"
  >
    {buttonText}
  </Link>

  <a href={link} className="icon-link" target="_blank">
    <i className="fa-solid fa-arrow-up-right-from-square"></i>
  </a>
</div>

Here is where I am trying to pass an array list into my useState

const softiesInfo = pageContentList.find(({id}) => id == 1);
const palcoInfo = pageContentList.find(({id}) => id == 2);
const tlaxInfo = pageContentList.find(({id}) => id == 3);

const [data, setData] = useState({})

const toggledItem = () => {
  if (id === 1) {
    setData(softiesInfo);
  } else if (id === 2) {
    setData(palcoInfo)
  } else {
    setData(tlaxInfo)
  }
};

Here is where my data should be rendered on my second page.

export default function DetailsPage() {
  const location = useLocation();
  const data = location.state;
  return (
    <div className="header-wrap">
      <h1 className="primary-ff">{data.title}</h1>
      <img src={data.logoImg} alt={data.altLogo} />
    </div>
  )
});

I tried force feeding the data into my useState like this and this resulted successful, but I don't see it being very efficient and would count as writing clean code.

const [data, setData] = useState({
  sectionName: "softies-section",
  title: "Softies Ice Cream-Under The Scoop",
  logoImg: softieslogo,
  altLogo: "softies-logo",
  img: softiesPlanner,
  altImg: "Career Day At Pinnacle",
  img2: softiesAccordion,
  constrainContent:
    "lorem a;sdkfj;alskdjfa lksdfjlaksjdl;fkjasldk;fjaslkdfjal;sdkjfalksdjf;laks;dj asflsngealj    sflsdajflkads",
  id: 1,
});

Solution

  • Issue

    The issue here is that the Link component effects a navigation action immediately when it is clicked, and the passed state prop will include whatever the data value is at the time the link is clicked.

    The DetailsPage also isn't accessing into the passed state reference the data property, it's only declaring a local variable named data and setting it to the value of location.state. DetailsPage would need to access data.data.title to get the passed information.

    Solution

    You can prevent the default link action from happening and effect the navigation action manually later. Unfortunately because enqueued React state updates are processed asynchronously you also can't use the data state in the route state. What you can do however is to enqueue the state update alongside the imperative navigation action with the same value.

    Example:

    import { Link, useNavigate } from 'react-router-dom';
    
    ...
    
    const navigate = useNavigate();
    
    ...
    
    const toggledItem = (e) => {
      e.preventDefault(); // <-- prevent link navigation
    
      let data = {};
    
      switch(id) {
        case 1:
          data = softiesInfo;
          break;
    
        case 2:
          data = palcoInfo;
          break;
    
        case 3:
        default:
          data = tlaxInfo;
          break;
      }
    
      setData(data);
      navigate( // <-- imperative navigation
        "/DetailsPage",
        { state: { data } }
      );
    };
    

    ...

    <Link
      to="/DetailsPage"
      onClick={toggledItem}
      className="primary-ff boxyButton"
    >
      {buttonText}
    </Link>
    
    export default function DetailsPage() {
      const location = useLocation();
      const { data } = location.state ?? {};
    
      return (
        <div className="header-wrap">
          <h1 className="primary-ff">{data.title}</h1>
          <img src={data.logoImg} alt={data.altLogo} />
        </div>
      )
    });