Let's say I have 2 components, app and project
I want to have control of elements in my project component from my app component.
When not using a route I can just do the following in my app component
const App = () => {
const ProjectRef = useRef(null);
return (
<Project ref={projectRef}>
);
}
Then in my project component I can get the reference as such
const Project = ({}, ref) => {}
My issue is how to achieve this when using a route. I have tried the following in my app.js component:
<Switch>
<Route path="/projects/:slug">
<Project ref={projectRef}/>
</Route>
</Switch>
This works in that I can get the ref, however in this method I can't seem to get access to the information about the route, which I need for this dynamic route.
Previously I did the following to get the slug
const Project = ({ match, location }) => {
// Get slug
const {params: { slug }} = match;
}
I tried the following, but it does not work, match is empty.
const Project = ({ match, location }, ref) => {}
I am looking for a way to achieve this ideally with functional components.
You are not passing the router props to the defined router component Project. So, just passing props to the component should do the work in your case.
Just replace this
<Switch>
<Route path="/projects/:slug">
<Project ref={projectRef}/>
</Route>
</Switch>
with this
<Switch>
<Route
path="/projects/:slug"
render={(props) => <Project {...props} ref={projectRef} />}
/>
</Switch>
Related
Am using react router v6 and i would like to use the new loader to load the data before the component loads. So i have the following
In my index.js
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="*"
loader={async ({ params }) => {
console.log("index loader log"); //this logs
return true;
}}
element={<App />}
> </Route>
)
);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<RouterProvider router={router} />
);
in my app component i have nested routes like
const App = () => {
return (
<>
<Routes>
<Route path="auth/*" element={<AuthLayout/>}/>
<Route path="about"
loader={async ({ params }) => {
console.log("about child loader log"); //this doesnt log
return true;
}}
element={<AboutPage/>}/>
</Routes>
<h1>Testing app</h1>
</>
);
}
On the app component the loader on the Route path="about" does not console.log when i visit the about route but the component is rendered. What am i missing for the loader to work on the child route.
Based on some basic testing it seems that in order for the new RRDv6.4 data APIs to work you need to specify the complete routing configuration in the createBrowserRouter function.
There does however appear to already be an issue filed with #remix-run/react-router for this behavior as a reported bug, so you may want to follow it if it ever addressed/resolved. (I suspect it was you since the name is "geoffrey" and the timing is coincidentally about an hour ago around the same time as this post)
This above issue has since been closed with comment:
Descendant <Routes> trees do not participate in data loading
(https://reactrouter.com/en/main/components/routes) since they cannot
be known ahead of render-time. You'll need to lift your descendant
route definitions up into the routes you pass to createBrowserRouter.
The relevant information regarding the descendent routes and the new Data API can be found in the Routes documentation in a note.
Note:
If you're using a data router like createBrowserRouter it is
uncommon to use this component as it does not participate in data
loading.
Hoist the entire route declaration to the parent creating the data router. The following does work with the loader function for the "/about" route and About component.
const router = createBrowserRouter(
createRoutesFromElements(
<Route
path="*"
loader={({ params }) => {
console.log("index loader log");
return "This is the App";
}}
element={<App />}
>
<Route path="auth/*" element={<AuthLayout />} />
<Route
path="about"
loader={({ params }) => {
console.log("about child loader log");
return "this is the about page";
}}
element={<AboutPage />}
/>
</Route>
)
);
The App component should render an Outlet for the nested routes to render their content into.
import { Outlet } from 'react-router-dom';
const App = () => {
return (
<>
<h1>Testing app</h1>
<Outlet />
</>
);
};
Using react 18.2 with react-router-dom 6.3
I have my routes in this style
<Routes>
<Route path="/" element = {<App/>}>
<Route path="search" element = {<Content/>} />
<Route path="nextComp/:id" element = {<Component/>} />
</Route>
</Routes>
In my app, I have a dynamic nav bar that I want to stick around which could be used to select different generated components (generated by actions in the Content component in /search) There are states in my App component that need to be set by the Content component. I pass information down with something along the lines of:
const App: React.FC = () => {
const [lock, setLock] = useState<Boolean>(false);
const [lotsQuery, setLotsQuery] = useState<lotWaferQueryInput[]>([]);
const navigate = useNavigate();
const onClickHandle(() => {
navigate('/search', {state: {
setLock : setLock,
setLotsQuery: setLotsQuery
}});
}, []);
}
In my Content component, I try accessing the data with :
const {state} : any = useLocation();
const {setLock,setLotsQuery} : any = state;
This results in
Uncaught TypeError: Cannot destructure property 'setLock' of 'state' as it is null. I understand that you can't directly serialize a function. How should I reapproach the way I'm routing data?
As of now, the hierarchy looks like
App
-Nav (child component)
-Content(search subroute)
-Component(nextComp subroute)
Data is entered in Content, and then sent to App (which is the current problem of being able to set function). Data is handled by App and then passed to nav and generates Component(subroutes)
How can I achieve sending data from a subroute component to a parent route then? Any advice appreciated.
The route state needs to be JSON serializable, so sending functions just won't work. I suggest exposing the functions down to nested routes via the Outlet's context and the useOutletContext hook.
Example:
import { Outlet } from 'react-router-dom';
const App: React.FC = () => {
const [lock, setLock] = useState<Boolean>(false);
const [lotsQuery, setLotsQuery] = useState<lotWaferQueryInput[]>([]);
...
return (
...
<Outlet context={{ setLock, setLotsQuery }} />
...
);
};
In nested route's component:
import { useOutletContext } from 'react-router-dom';
...
const { setLock, setLotsQuery } = useOutletContext();
...
<Routes>
<Route path="/" element={<App />}> // <-- provides context value
<Route path="search" element={<Content />} /> // <-- can access context value
<Route path="nextComp/:id" element={<Component />} />
</Route>
</Routes>
I have the following routes:
const makeIndexRoutes = (): React.ReactElement => (
<Switch>
<Redirect exact from="/" to="/estimates" />
<Route exact path="/estimates" component={CostingPage} />
<Route exact path="/estimates/new" component={NewEstimatePage} />
<Route exact path="/estimates/edit/:id" component={EditEstimatePage} />
</Switch>
);
And in another file I try to make a redirect on button click like this:
const handleClose = useCallback(() => {
// do some action on 'close' button click
<Redirect to='/estimates'></Redirect>
}, []);
But nothing happens, is anyone able to guide me on what I am potentially doing wrong?
This won't work, what you want to is programmatically redirect.
For this, you should do something like this:
import { useHistory } from 'react-router-dom';
const history = useHistory();
const handleClose = () => {
history.push('/estimates');
});
What you are doing will not work because Redirect component should be rendered in JSX in order for it to work and change the route.
You can use one of the following options to change the route
Use history object from router props
props.history.push('/estimates')
you could also use useHistory hook provided by react router to access the history object.
Use Link component provided by react router. It will automatically change the route without needing a click listener
<Link to="/estimates">Estimates</Link>
For more details see:
Link Component
history
I've a condition where I've 3 components
A.js B.js C.js
In old code
routes.js
const A = lazy(() => import("../test/A"));
const B = lazy(() => import("../test/B"));
const C = lazy(() => import("../test/C"));
<Route
path="/test"
render={({ match: { url } }) => (
<Switch>
<Route exact path={`${url}/a-test`} component={A} />
<Route exact path={`${url}/b-test`} component={B} />
<Route exact path={`${url}/c-test`} component={C} />
</Switch>
)}
/>
What I want to achieve
By the above code we can conclude that every time the separate builds
for A, B, C will get download due to lazy load, but I want to achieve
a functionality where I can download all 3 of them altogether, i.e.
When a download a single component using that only I can download all
three of them as a single separate build
What I tried - New Code
combine_module.js
import A from "./A";
import B from "./B";
import C from "./C";
const test = {A, B, C};
export default test;
routes.js
const Test = lazy(() => import("../test/combine_module"));
<Route
path="/test"
render={({ match: { url } }) => (
<Switch>
<Route exact path={`${url}/a-test`} component={Test.A} />
<Route exact path={`${url}/b-test`} component={Test.B} />
<Route exact path={`${url}/c-test`} component={Test.C} />
</Switch>
)}
/>
Code
Full code link
My Issue
I don't know about the approach whether it's wrong or right as I'm not getting any error or any warning related to this in the console but seeing a blank screen cannot see the output, So can please anybody tell how to make this thing right and achieve the desired functionality.
According to the React.lazy documentation:
React.lazy takes a function that must call a dynamic import(). This must return a Promise which resolves to a module with a default export containing a React component.
So the result of React.lazy is not a module but a special object Suspense can work with.
A usual module can be loaded lazily with WebPack's dynamic import. There is a tricky part with this because the module can be loaded after the component was rendered. So two things we need to do:
1) ensure the component does not render undefined while module is being loaded
2) update the component when the module has been loaded
Updating is quite simple we can make a hook that will set the module as state on load
function useDynamicModule(path) {
const [module, setModule] = useState({});
const [error, setError] = useState();
useEffect(() => {
import(path).then(setModule, setError);
}, []);
return error ? { $error: error } : module.default
}
Usage:
export default function App() {
const Test = useDynamicModule("./CombineModule");
if (!Test) return null;
if (Test.$error) return "Error";
return (
<Router history={history}>
// ...
</Router>
);
}
Playground
I'm trying to handle a route like this
<Route path="/:first/:second?/:third?" />
where the amount of optional params after "first" are unknown. What's the best practice for approaching this in React Router v4?
Maybe you can do something fancy with path-to-regexp which is what react router uses to resolve paths, but making a custom route that parses the location and reformats it to dynamic paths seems pretty easy!
let NoArityRoute = ({ component }) => (
<Route
children={({ location }) => {
let params = location.pathname.split("/");
let first = params[1];
let rest = params.slice(2);
let path = `/:${first}${rest.map(x => `/:${x}?`).join("")}`;
return <Route path={path} component={component} />;
}}
/>
);
Then wherever else:
<NoArityRoute
component={({ match }) => (
<div>params: {JSON.stringify(match.params)}</div>
)}
/>
where match.params will be any number of dynamic args based on the url.