I'm working on a project with react and i'm doing a simple history.push()
just to navigate to another component. Beside navigating to the other component i'm passion some data by using history.push({state: []})
.
To access the data to the other component I use useLocation()
and access it with location.state.
I switch the language on the app (the language logic is set with setLocalStorage()
) and suddenly the location.state
gets undefined
.
I read in some articles that location.state
gets undefined on re render. I didn't find something clear that specify that, not even on the react-router-dom
documentation. Also when I reload the page the location.state
doesn't become undefined
.
My question is why does it happen when the page is re render.? Is it the best practice to pass the data like this? If not which is the best practice to pass it to another component so these errors can be avoid?
LanguageDropdown
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { Dropdown } from 'react-bootstrap';
import classNames from 'classnames';
import enFlag from './flags/uk.jpg';
import italyFlag from './flags/italy.jpg';
import { useTranslation } from 'react-i18next';
// get the languages
const Languages = [
{
name: 'English',
flag: enFlag,
short_name: 'en'
},
{
name: 'Italian',
flag: italyFlag,
short_name: 'it'
}
];
const LanguageDropdown = () => {
const [dropdownOpen, setDropdownOpen] = useState<boolean>(false);
const [selectedLanguage, setSelectedLanguage] = useState(
localStorage.getItem('defaultLang') !== null
? Languages.find(function (item: { name: string; flag: string; short_name: string }) {
return item.short_name == localStorage.getItem('defaultLang');
})
: Languages[0]
);
const [t, i18n] = useTranslation('common');
/*
* toggle language-dropdown
*/
const toggleDropdown = () => {
setDropdownOpen(!dropdownOpen);
};
const changeLanguage = async (lang: any) => {
i18n.changeLanguage(lang.short_name);
setSelectedLanguage(lang);
localStorage.setItem('defaultLang', lang.short_name);
};
return (
<Dropdown show={dropdownOpen} onToggle={toggleDropdown}>
<Dropdown.Toggle
id='dropdown-languages'
as='a'
onClick={toggleDropdown}
className={classNames('nav-link waves-effect waves-light', {
show: dropdownOpen
})}>
<img src={selectedLanguage.flag} alt={selectedLanguage.name} height='16' />
</Dropdown.Toggle>
<Dropdown.Menu className='dropdown-menu dropdown-menu-end'>
<div onClick={toggleDropdown}>
{(Languages || []).map((lang, i) => {
return (
<Link
to='#'
onClick={() => changeLanguage(lang)}
className='dropdown-item notify-item'
key={i + '-lang'}>
<img src={lang.flag} alt={lang.name} className='me-1' height='12' />
<span className='align-middle'>{lang.name}</span>
</Link>
);
})}
</div>
</Dropdown.Menu>
</Dropdown>
);
};
export default LanguageDropdown;
The issue appears to be the link targets used in the language selection dropdowns. They issue a navigation action to "/#"
sans any existing state. This is the route state being wiped out.
Instead of navigating to "/#"
you may just navigate to the current location
const LanguageDropdown = () => {
const location = useLocation(); // <-- current location, including any state
...
return (
<Dropdown show={dropdownOpen} onToggle={toggleDropdown}>
...
<Dropdown.Menu className='dropdown-menu dropdown-menu-end'>
<div onClick={toggleDropdown}>
{(Languages || []).map((lang, i) => {
return (
<Link
to={location} // <-- current location, including any state
onClick={() => changeLanguage(lang)}
className='dropdown-item notify-item'
key={i + '-lang'}
>
...
</Link>
);
})}
</div>
</Dropdown.Menu>
</Dropdown>
);
};
export default LanguageDropdown;
If you are effectively staying on the current page though, you could also just omit the to
prop and cancel the navigation action.
const LanguageDropdown = () => {
...
return (
<Dropdown show={dropdownOpen} onToggle={toggleDropdown}>
...
<Dropdown.Menu className='dropdown-menu dropdown-menu-end'>
<div onClick={toggleDropdown}>
{(Languages || []).map((lang, i) => {
return (
<Link
onClick={(e) => {
e.preventDefault(); // <-- prevent navigation action
changeLanguage(lang);
}}
className='dropdown-item notify-item'
key={i + '-lang'}
>
...
</Link>
);
})}
</div>
</Dropdown.Menu>
</Dropdown>
);
};
export default LanguageDropdown;