React Router - Placing components by iteration not working - reactjs

Given the following code:
const AdmNews = React.lazy(() => import('./components/adm/AdmNews'));
const AdmPeople = React.lazy(() => import('./components/adm/AdmPeople'));
const AdminRoutes = [
{path:"/route/admin/news",component:<AdmNews/>},
{path:"/route/admin/people",component:<AdmPeople/>},
]
{AdminRoutes.map((el)=>{
return <Route path={el.path}
element={
<React.Suspense fallback={<>...</>}>
{el.component}
</React.Suspense>
}/>
})}
I'm trying to iterate over objects in order to create the routes in react, and when I click on the matching buttons, they understand the path property, and it reacts accordingly, however, the component is not loading. Is is right to have the component inside the object as show above: component:<AdmNews/>? (No errors are shown.)
PD. If I place the code manually without iteration, it works, so I suspect of component:<AdmNews/> not being the right approach.

Related

Transferring data from one page to another

I have a page called Games in my React app and my React Routes are in the App.js file. On that Games page, if I click myButton, I want it to go to another page called Analyze Game and then populate some variables there. But when I normally open Analyze Game (without clicking myButton), it defines and populates a bunch of state and variables. The reason is so that you can analyze a game you manually enter, rather than picking from the list on the Games page. So I am a bit puzzled on how I can transfer the game data from the Games page to the Analyze Game page and then populate some variables there only if someone came from the games page.
I found this link that shows you can use history.push to get some data that was passed from one page to another: React-router - How to pass data between pages in React?


But how do you then only populate the variables on the Analyze Game page if you came from the games page?
Would you set a flag or something? What is best practice?
This is a good question, and normally can be done in two ways. I'll briefly mention them. Of course, there're other ways ;)
props
React likes props, so if you can solve it with that, that should be your first approach.
const App = () => {
const [value, setValue] = useState(...)
return (
<Router>
<Route1 render={() => <GamePage value={value} />} />
<Route2 render={() => <AnalyzePage value={value} />} />
</Router>
)
}
If you want to change this value inside either Game or Analyze, pass the setValue inside as well.
context
If you have very nested components inside the route, sometimes people like to use a context.
const App = () => {
const [value, setValue] = useState(...)
return (
<Context.Provider value={{ value, setValue}}>
<Router>
<Route1 render={() => <GamePage />} />
<Route2 render={() => <AnalyzePage />} />
</Router>
</Context.Provider>
)
}
const ComponentInsideGame = () => {
const { value } = useContext(Context)
...
}
global
If it turns out you want to send something behind React, for instance you don't want to trigger render at all for these shared data. You could do a global context with a ref.
const App = () => {
const ref = useRef({
gameConfiguration: ...,
runGame: () => {}
))
return (
<Router>
<Route1 render={() => <GamePage />} />
<Route2 render={() => <AnalyzePage />} />
</Router>
)
}
const ComponentInsideGame = () => {
const { current } = useContext(Context)
current.gameConfiguration = {...}
current.runGame()
...
}
route state
The link you provided is another way with browser state, however this actually isn't a react way, it's a browser way IMHO. But you know what, anyway works for you is fine :)

Move between pages without losing pages content - React JS

I'm trying to navigate between pages without losing pages data. For instance, I have a page that contains many input fields, if a user filled all these fields and tried to move to another page and return back to the first one, all the inputs are gone. I am using react-router-dom but didn't find out a way to prevent that.
What I've done till now :
import { Route, Switch, HashRouter as Router } from "react-router-dom";
<Router>
<Switch>
<Route path="/home" exact component={Home} />
<Route path="/hello-world" exact component={HellWorld} />
</Switch>
</Router>
Home Component :
navigateToHelloWorld= () => {
this.props.history.push('/hello-world')
};
Hello World Component :
this.props.history.goBack();
I don't know that that can be supported in such generality. I would just store all state variable values in localStorage, and restore from there when values are present on component render (when using useState then as the default value). Something like this:
const Home = () => {
const [field, setField] = useState(localStorage.field || '');
const handleUpdate = (value) => {
setField(value);
localStorage.field = value;
}
// also add a submit handler incl. `delete localStorage.field`;
return ... // your input fields with handleUpdate as handler.
}
Generally, all you need to do is to store your data in someplace, either a component that doesn't unmount, like the component you are handling your routes in, which is not a good idea actually but it works!
another way is to use some kind of state manager like 'mobx','redux','mst', or something which are all great tools and have great documentation to get you started
another alternative is to store your data in the browser, for your example session storage might be the one to go for since it will keep data until the user closes the tab and you can read it in each component mount.

#cypress/react : How to launch a component under a given path / URL?

I have a React component 'Foo' that uses react-router to decide which child to mount.
Let's say this component has 3 submodules: A, B, C.
In a 'normal' cypress test I would use cy.visit('www.test.com/foo/subModuleA') and I would get my site and it would display my Foo-component, which would render the module 'A'.
My component test however mounts this component directly, and cy.visit(...) doesn't work.
cy.visit from a component spec is not allowed
see https://github.com/bahmutov/#cypress/react/issues/286
(Sadly this link leads to nowhere)
For the time being I wrote myself a helper-component:
function PathEnforcer (props: React.PropsWithChildren<{ path: string }>) {
const history = useHistory()
useEffect(() => {
history.push(props.path)
}, [])
return <>{props.children}</>
}
This simply switches the path as soon as it mounts.
Now I can do something like this in my test:
mount(
<HashRouter>
<PathEnforcer path='subModuleA'>
<Foo />
</PathEnforcer>
<HashRouter>
)
And my component is displaying the right module.
This is a workaround that I can work with, but it can hardly be a long term best practice.
How can I configure the tests / execute a command that directly mounts this component given the right path?
I was running into the same issue and this question came up. With react-router v6 this works:
mount (
<MemoryRouter initialEntries={['/foo']}>
<Routes>
<Route path={'/:segment'} element={<YourComponent />}>
</Routes>
</MemoryRouter>
)

Why is my custom hook not re-initialised on path change?

I am using ReactRouter to route the application (BrowserRouter at the top level) with a Switch that includes all the routes.
In my use-case I want to be able to handle paths that include the path-parameters (bedId) and navigate between different sub-paths (i.e. /beds/:bedId/, /beds/:bedId/info/) as well asa case where the path is (/beds/-).
I also want to be able to direct user to a different "bed" while they are already on some bed, so /beds/bed1/info -> /beds/bed2, and so on...
My BedView component is responsible for routing within that /beds/:bedId path like so:
// App.jsx (fragment)
<Switch>
<Route
path="/"
exact
render={() => (<Redirect to="/beds/-"/>)}
/>
<Route
path="/beds/-"
exact
component={SomeOtherComponent}
/>
<Route
path="/beds/:bedId"
component={BedView}
/>
</Switch>
The problem occurs when I try to use a hook that relies on the current path-parameter to fetch the latest data (i.e. call to /beds/bed1 will result in a call to http://myapi.com/beds/bed1/timeseries). The useLatestData hook is called from the BedView component, which look like so:
// BedView.jsx (fragment)
export default function BedView(props) {
const {bedId} = props.match.params;
let {path, url} = useRouteMatch('/beds/:bedId');
const bedData = useLatestData({
path: `/beds/${bedId}/timeseries`,
checksumPath: `/checksum/timeseries`,
refresh: false
});```
if(!bedData){
return <Loading/>;
}
return (
<Switch>
<Route exact path={path}>
<Redirect to={`${url}/info`}/>
</Route>
<Route exact path={`${path}/info`} >
<SomeComponent info={bedData.info} />
</Route>
</Switch>
}
...and the useLatestData hook is available here.
The problem is the fact that upon redirecting from /beds/bed1/info to /beds/bed2/info, the hook does not update its props, even though the BedView component seems to be re-rendering. I have created a version of the hook that 'patches' the problem by adding an useEffect hook in my custom hook, to detect the change in path (as supplied in the function arguments) and set data to null, but then the behaviour changes on the BedView.jsx's end - making the following check fail:
if(!bedData){
return <Loading/>;
}
I'm not entirely sure which part is the culprit here and any guidance would be much appreciated! As far as I'm aware, the hook is no re-initialised because the path change still results in the same component. There is also one caveat, once I change the BrowserRouter to include the forceRefresh flag, everything works fine. Naturally, I don't want my page to refresh with every redirect though...
try this:
const {bedId} = props.match.params;
let {path, url} = props.match;

Passing data between Router Components in React

I have a react-router-dom based application where I currently have two screens: one for the initial setup and a second one that needs some information from the first. That information is stored in constants in the Home page's code, and I would like to know if there is a method for sharing the variables between them, or maybe passing them into the second in some way. The code and tree below provide a basic run-down of how I have it set up.
Structure:
App
|
Routes
|
BrowserRouter
____________________|____________________
| |
Route Route
Home ———— history.push("/in-game") ———> InGame
(+data)
Home code snippet:
const history = useHistory();
const toGame = () => history.push('/in-game');
const [colors, setColors] = useState();——————————
const [map, setMap] = useState();————————————————|—————————data I'd like to share with the InGame component
const [myColor, setMyColor] = useState();————————
Really hoping I can get a hand with this. Thanks!
you can use history object for this.
to change route from "home" to "ingame" with additional data to be passed into "ingame",
Home.js
const someEventHandler = event => {
history.push({
pathname: '/in-game',
state: { map: map }
});
};
to access this in "InGame",
InGame.js
const location = useLocation();
console.log(location.state.map); //map value is available here
try above. for more on history, https://reactrouter.com/web/api/history refer these docs from react-router.
Does this help
<Router>
<Switch>
<Route
path="/in-game/:data/:data2"
render={(props) => <InGame {...props} />}
/>
</Switch>
</Router>
This implies that you can pass two arguments in the URL in-game as a get parameter. These 2 params can be parsed in InGame component as this.props.match.params
To call this URL anywhere, you can use (example)
Click me
or
history.push("/in-game/param1/param2")
Refer this post: How to access 'this.props.match.params' along with other props?

Resources