We use cookies to enhance your experience on the site
CodeWorlds

Passing State Through Locations in Intergalactic Communication

On a cosmic journey, we sometimes need to send important data from one sector of the space station to another. In React applications using React Router, we similarly pass state (data) between different pages using the

location state
mechanism. Let's see how to use this mechanism to improve our intergalactic communication.

What is Location State?

Location state is a mechanism in React Router that allows passing data during navigation between different paths without the need to use URL parameters, cookies, or global application state. This data is not visible in the URL, but is available on the target page.

Passing State Through Link

The simplest way to pass data through location state is to use the

Link
component with an additional
state
parameter:

1import { Link } from 'react-router-dom';
2
3function PlanetList() {
4  const planets = [
5    { id: 1, name: 'Mars', atmosphere: 'Thin, mainly CO2', temperature: 'From -125°C to 20°C' },
6    { id: 2, name: 'Venus', atmosphere: 'Dense, mainly CO2', temperature: 'Average 462°C' },
7    { id: 3, name: 'Titan', atmosphere: 'Dense, nitrogen and methane', temperature: 'Average -179°C' }
8  ];
9
10  return (
11    <div className="planet-explorer">
12      <h1>Planet Exploration</h1>
13      <ul className="planet-list">
14        {planets.map(planet => (
15          <li key={planet.id}>
16            <Link
17              to={`/planet/${planet.id}`}
18              state={{ planetData: planet }}
19            >
20              Explore {planet.name}
21            </Link>
22          </li>
23        ))}
24      </ul>
25    </div>
26  );
27}

In the example above, clicking a link will navigate the user to page

/planet/1
(for Mars) and simultaneously pass the
planetData
object containing all information about the planet.

Receiving State in the Target Component

To read the passed state in the target component, we use the

useLocation
hook:

1import { useParams, useLocation } from 'react-router-dom';
2
3function PlanetDetails() {
4  // Get the parameter from the URL
5  const { id } = useParams();
6
7  // Get the location object, which contains the passed state
8  const location = useLocation();
9
10  // Extract the planet data from the location state, with a fallback to an empty object
11  const { planetData } = location.state || {};
12
13  // If we didn't receive planet data, we can show an error message
14  // or try to fetch data based on the ID
15  if (!planetData) {
16    return (
17      <div className="error-message">
18        <h2>No Planet Data</h2>
19        <p>Failed to load data for planet with ID: {id}</p>
20        <Link to="/">Return to planet list</Link>
21      </div>
22    );
23  }
24
25  return (
26    <div className="planet-details">
27      <h1>Planet Details: {planetData.name}</h1>
28
29      <div className="planet-info">
30        <p><strong>Atmosphere:</strong> {planetData.atmosphere}</p>
31        <p><strong>Temperature:</strong> {planetData.temperature}</p>
32      </div>
33
34      <Link to="/">Return to planet list</Link>
35    </div>
36  );
37}

Programmatic State Passing

We can also pass state programmatically using the

useNavigate
hook:

1import { useNavigate } from 'react-router-dom';
2
3function MissionControl() {
4  const navigate = useNavigate();
5
6  const startMission = (mission) => {
7    // Perform some operations before navigating to the next page
8    const missionData = {
9      ...mission,
10      startDate: new Date().toISOString(),
11      commander: 'Captain Alex Chen',
12      status: 'In Progress'
13    };
14
15    // Redirect to the mission page with data
16    navigate(`/mission/${mission.id}`, {
17      state: { missionData }
18    });
19  };
20
21  return (
22    <div className="mission-control">
23      <h1>Mission Command Center</h1>
24
25      <div className="mission-list">
26        <div className="mission-card">
27          <h3>Mission: Mars Exploration</h3>
28          <p>Objective: Soil sample research</p>
29          <button onClick={() => startMission({
30            id: 'mars-exploration-1',
31            name: 'Mars Exploration',
32            objective: 'Soil sample research'
33          })}>
34            Start Mission
35          </button>
36        </div>
37
38        <div className="mission-card">
39          <h3>Mission: Satellite Station</h3>
40          <p>Objective: Communication systems repair</p>
41          <button onClick={() => startMission({
42            id: 'satellite-repair-1',
43            name: 'Satellite Station',
44            objective: 'Communication systems repair'
45          })}>
46            Start Mission
47          </button>
48        </div>
49      </div>
50    </div>
51  );
52}

Advanced Use Cases

1. Passing Previous Location Information

We can use location state to remember the page the user came from:

1function SecureArea() {
2  const navigate = useNavigate();
3  const { user } = useAuth();
4
5  useEffect(() => {
6    if (!user) {
7      // Redirect to login with information about the current path
8      navigate('/login', {
9        state: { from: location.pathname },
10        replace: true
11      });
12    }
13  }, [user, navigate, location]);
14
15  return (
16    <div className="secure-area">
17      <h1>Secure Section of the Spaceship</h1>
18      {/* Secure section content */}
19    </div>
20  );
21}
22
23// In the login component
24function Login() {
25  const location = useLocation();
26  const navigate = useNavigate();
27
28  // Get the original path the user tried to access
29  const from = location.state?.from || '/dashboard';
30
31  const handleLogin = async (credentials) => {
32    const success = await performLogin(credentials);
33
34    if (success) {
35      // After login, return to the original page
36      navigate(from, { replace: true });
37    }
38  };
39
40  // Rest of the login component...
41}

2. Passing Form Data in a Multi-step Process

Another advanced case is preserving form data during navigation in a multi-step process:

1function SpaceshipConfigStep1() {
2  const [config, setConfig] = useState({
3    name: '',
4    type: 'exploration',
5    crew: 4
6  });
7
8  const navigate = useNavigate();
9
10  const handleNext = () => {
11    // Form validation...
12
13    // Navigate to the next step with data
14    navigate('/config/step2', {
15      state: { config }
16    });
17  };
18
19  return (
20    <div className="config-form">
21      <h2>Spaceship Configuration - Step 1</h2>
22      <form onSubmit={e => { e.preventDefault(); handleNext(); }}>
23        <div className="form-field">
24          <label htmlFor="name">Ship name:</label>
25          <input
26            id="name"
27            value={config.name}
28            onChange={e => setConfig({...config, name: e.target.value})}
29            required
30          />
31        </div>
32
33        <div className="form-field">
34          <label htmlFor="type">Mission type:</label>
35          <select
36            id="type"
37            value={config.type}
38            onChange={e => setConfig({...config, type: e.target.value})}
39          >
40            <option value="exploration">Exploration</option>
41            <option value="science">Scientific Research</option>
42            <option value="mining">Resource Mining</option>
43          </select>
44        </div>
45
46        <div className="form-field">
47          <label htmlFor="crew">Crew size:</label>
48          <input
49            id="crew"
50            type="number"
51            min="1"
52            max="10"
53            value={config.crew}
54            onChange={e => setConfig({...config, crew: parseInt(e.target.value)})}
55            required
56          />
57        </div>
58
59        <button type="submit">Next</button>
60      </form>
61    </div>
62  );
63}
64
65function SpaceshipConfigStep2() {
66  const location = useLocation();
67  const navigate = useNavigate();
68
69  // Get configuration from the previous step
70  const { config: prevConfig } = location.state || { config: null };
71
72  // If we don't have configuration, redirect back
73  useEffect(() => {
74    if (!prevConfig) {
75      navigate('/config/step1', { replace: true });
76    }
77  }, [prevConfig, navigate]);
78
79  // Initialize state with data from the previous step
80  const [config, setConfig] = useState({
81    ...prevConfig,
82    engines: 'ion',
83    fuelCapacity: 5000,
84    lifeSupport: 'advanced'
85  });
86
87  const handleFinish = () => {
88    // Complete configuration from both steps
89    const finalConfig = {
90      ...config
91    };
92
93    // Finalize configuration
94    navigate('/config/summary', {
95      state: { finalConfig }
96    });
97  };
98
99  if (!prevConfig) return null; // Don't render anything until the redirect executes
100
101  return (
102    <div className="config-form">
103      <h2>Spaceship Configuration - Step 2</h2>
104      <form onSubmit={e => { e.preventDefault(); handleFinish(); }}>
105        {/* Second set of form fields */}
106
107        <div className="form-actions">
108          <button
109            type="button"
110            onClick={() => navigate(-1)}
111          >
112            Back
113          </button>
114          <button type="submit">Finish</button>
115        </div>
116      </form>
117    </div>
118  );
119}

The replace Function and Browser History

In the examples, we used the

replace: true
parameter. This is an important option that affects browser history behavior:

  • When
    replace: false
    (default) - adds a new page to history. The user can return to the previous page with the "Back" button.
  • When
    replace: true
    - replaces the current page in history. When the user presses the "Back" button, they will return to the page before the last redirect.
1// Adds a new page to browser history
2navigate('/dashboard', { state: { data: 'example' } });
3
4// Replaces the current page in history
5navigate('/dashboard', {
6  state: { data: 'example' },
7  replace: true
8});

The

replace
option is particularly useful in login processes, where after redirecting the user to the login page and back, we don't want them to be able to return to the login page using the "Back" button.

Limitations and Alternatives

Location state has certain limitations:

  1. Not persistent - data is lost on page refresh
  2. Limited size - we shouldn't pass very large objects
  3. Not visible in URL - which can be an advantage (privacy) or disadvantage (can't remember/share the exact state)

Alternative methods of passing data between pages:

  1. URL parameters: Data is visible in the URL address

    1// Passing through URL
    2navigate(`/planet/mars?atmosphere=thin&temperature=-60`);
    3
    4// Reading
    5const { search } = useLocation();
    6const queryParams = new URLSearchParams(search);
    7const atmosphere = queryParams.get('atmosphere'); // 'thin'
  2. Global application state: State management using Context API, Redux, Zustand, etc.

    1// Saving data
    2dispatch(setCurrentPlanet({ name: 'Mars', atmosphere: 'thin' }));
    3navigate('/planet-details');
    4
    5// Reading data
    6const currentPlanet = useSelector(state => state.planets.current);
  3. Local storage: sessionStorage, localStorage

    1// Saving data
    2sessionStorage.setItem('planetData', JSON.stringify({ name: 'Mars' }));
    3navigate('/planet-details');
    4
    5// Reading data
    6const planetData = JSON.parse(sessionStorage.getItem('planetData') || '{}');

Best Practices

  1. Minimal data scope: Pass only necessary data
  2. Data validation: Always check if data exists before using it
  3. Provide a fallback: Have a plan B when data is not available
  4. Avoid sensitive data: Don't pass confidential information through location state
  5. Use type safety: If using TypeScript, define types for data in location state

Summary

Passing state through locations in React Router is like intergalactic communication - we can send important data between different "stations" (pages) of our application without having to store them globally.

This technique is particularly useful in cases such as:

  • Passing object details to a detail page
  • Remembering the original location during authentication redirects
  • Preserving form state in multi-step processes
  • Passing messages and notifications between pages (e.g., "Operation completed successfully")

When choosing between location state and other data passing methods, always consider the persistence, visibility, and size of the data to choose the best approach for the specific use case.

Go to CodeWorlds