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.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.
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.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}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}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}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}In the examples, we used the
replace: true parameter. This is an important option that affects browser history behavior:replace: false (default) - adds a new page to history. The user can return to the previous page with the "Back" button.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.Location state has certain limitations:
Alternative methods of passing data between pages:
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'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);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') || '{}');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:
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.