How use same context on child routes in react-router v3? - reactjs

I need to use the same context over different routes. This is possible in the latest versions of react-router, but how can I achieve the same in react-router-v3? Here, is the sample code:
{
path: "/",
getChildRoutes(_: LocationState, cb: RoutesCallback) {
const routes = require("./routes").default;
cb(null, routes());
},
}
I need to add the same ContextProvider for all the child routes. Currently, I render a Layout component which is responsible to change the child components based on state value. The function is in the context. But I need to render these child components as independent routes and wrap all of them with the same context which I am not sure how can I achieve.

Related

How can I know if specific parent React component exists?

So I've got a component that is dependent on the context of BrowserRouter to hook into
<BrowserRouter>
<...>
<.../>
<.../>
<MyRedirectComponent/>
<.../>
</...>
</BrowserRouter>
I would love to simply include BrowserRouter inside my MyRedirectComponent that way I wouldn't need to wrap it all the time.
const MyRedirectComponent = () => {
const browserRouterParentExists = // I dunno
return browserRouterParentExists ? (
<NormalStuff/>
) : (
<BrowserRouter>
<NormalStuff/>
</BrowserRouter>
)
}
Is correctly populating browserRouterParentExists possible?
Writing conditional logic that tries to access parent component is an anti-pattern in React IMO and I don't think there is a public API you can use for that purpose.
I didn't get what you are trying to achieve but BrowserRouter is defined as
A <Router> that uses the HTML5 history API (pushState, replaceState
and the popstate event) to keep your UI in sync with the URL.
and most of the time you only need one BrowserRouter in your app in a top level component like App considering its usage. So wrapping a component with it and using that component throughout your app is not quite reasonable. If you are trying to redirect user to another route based on some condition, you can use any data coming from props, state, Context API or a state management lib. like Redux etc. to implement your logic and render Redirect component together with that conditional logic.

Update Redux State based on Reach Router Component

I want to update the state of my application when a router component is rendered. For example, if I go home "/" I will render homepage, but I want to trigger a state change so that other components like my footer and header are aware we are on the home page vs internal page. Currently, I am parsing the url manually when site is loaded to detect this, however, as the site grows this is unfeasible. I have also seen onEnter as a solution, however, this is not available for reach router.
<Router basepath={process.env.PUBLIC_URL} className="app" >
<LandingPageBody path="/" default/>
//update redux state to show we are on homepage
</Router>
You can use componentDidMount in LandingPageBody component. It is a react lifecycle method it will be triggered when component will mount. You can update state from inside of this method.
If you are using functional components, make use of useEffect function with dependency on props.match.location. Check for condition in that and dispatch action if the condition is true.
(React.useEffect(() => { if(props.match.location === 'desired path') dispatch(action) }, [props.match.location]))
This way when ever your route component loads, it will check for route on every route change and let your redux store know about the current path.
For Stateful component, you can do the same in componentDidUpdate lifecycle.

sharing data between disconnected components

I'm not creating a react app from scratch, but adding interactive components to an existing webpage. I'm mounting two components, disconnected to each other like this:
ReactDOM.render(<Component1 />, document.getElementById('comp1-root'));
ReactDOM.render(<Component2 />, document.getElementById('comp2-root'));
They lie far from each other on the page.
How do I have them to communicate their states with each other?
Thank you.
React Portals is what I was looking for.
As per my question, I wanted to have same context for all the components mounted at different locations (dom nodes).
Portals solved exactly this issue. Now, I can have one context component housing all the components that exist on that page. Like this:
const dashboardContextDom = document.getElementById('dashboard-root');
const comp1DOM = document.getElementById('comp1-root');
const comp2DOM = document.getElementById('comp2-root');
const Dashboard = () => {
return (
<>
{ReactDOM.createPortal(<Component1 />, comp1DOM)}
{ReactDOM.createPortal(<Component2 />, comp2DOM)}
</>
);
}
if(dashboardContextDom) {
ReactDOM.render(<Dashboard />, dashboardContextDom);
}
With these components housed in one context allows to easily pass state from one component to another via prop drilling and lifting state up.
You have two options with React:
Move Component 1 and Component 2 and their state into a parent component, and pass the parent's update state function to them:
ComponentWrapper {
...
const updateState = () => {
//update state
}
return (
<Component 1 updateParentState={updateState}/>
<Component 2 updateParentState={updateState}/>
)
...
}
You cannot update the state of a component that is not a parent or a child without using an external state management solution, such as Redux or the new useContext api/hook. These libraries involve moving all individual component's states to a larger centralized global state, which can be updated and accessed by all components.

Can useParams be able to use inside App.js?

Good day, hoping that I am not bothering you guys.
Details
I have a path like this
localhost:3000/Project-1/todo-1
and Switch of Route like this
<Route path='/:project/:todo' component={Todo} />
Expected Output
When browser is in the example path, what I expected is App.js can also get params object by using useParams but shows empty. Did I misuse the hook? Thank you for the answers.
Additional Details
I did use useLocation that returns something like path but that is not my aim, it should be something like
params: {
project: 'Project-1',
todo: 'todo-1 '
}
that is returned using useParams for ease of extraction of project and todo values
I believe Route injects props to components it renders, so you should be able to pull it straight from the match prop within Todo: this.props.match.params.
You’ll have access to match objects in various places:
Route component as this.props.match
Route render as ({ match }) => ()
Route children as ({ match }) => ()
withRouter as this.props.match
matchPath as the return value
https://reacttraining.com/react-router/web/api/match
This only works for components rendered within a router on a route, i.e. the component a Route is rendering, or a child component further down the DOM tree. If App.js is rendering the router it won't have it.
If you need to do something with route params in the root App.js you can wrap it in a route, with unspecified path so it matches all routes, in your router using the render prop. Just ensure this is rendered outside any Switch components though as switches only return the first match, not all matches.
<Route
render={({ match }) => {
console.log('match', match);
// do what you need with the `match` prop, i.e. `match.params`
return null; // return null to indicate nothing should actually be rendered
}}
/>

How to pass props to react-router 1.0 components?

Please note, that although the question itself is largely a duplicate of this, it concerns a different version which should support this. The linked question already accepted an answer on an old version
I'm pretty confused about what the intended workflow is.
Let's say I have a menu system where clicking on each item uses react-router to navigate to an area which pulls some data from the server.
url: yoursite/#/lists/countries
----------------------------
Billing Codes | <<Countries>> | Inventory Types
---------------------------------------------------------
Countries:
---------------
Afghanistan
Azerbaijan
Belarus
with routes something like
Route #/lists component: Lists
Route billing-codes component: BillingCodes
Route countries component: Countries
Route inventory-types component: InventoryTypes
I don't want to preload data from the server until an area is navigated to, so in my Countries component's on componentWillMount I fire an event (I'm using reflux but...whatever) that triggers a store to do an ajax request and update itself with the current list of countries.
Now the Countries component reacts to that change in state by updating the countries in its props. Except - reasonably - that generates an invariant error because I shouldn't be updating props on a child component, I should update it at the top level. But the top level is the router itself so now I'm just lost - where am I supposed to listen to changes and update props from?
(Cross-posted to the issue tracker as I think it needs some clearer documentation)
Reading the react-router 0.13 -> 1.0 Upgrade Guide and this example led me to the following:
{ this.props.children &&
React.cloneElement(this.props.children, {newprop: this.state.someobject }) }
Instead of including the child components directly, we clone them and inject the new properties. (The guard handles the case where there is no child component to be rendered.) Now, when the child component renders, it can access the needed property at this.props.newprop.
The easy way is to just use this.state, but if you absolutely have to use this.props then you should probably extend Router.createElement.
First add the createElement prop to your Router render.
React.render(
<Router history={history} children={Routes} createElement={createElement} />,
document.getElementById('app')
);
Wrap all of your components in a new Container component.
function createElement(Component, props) {
return <Container component={Component} routerProps={props} />;
}
Your Container component will probably look something like this. Pass an onLoadData function down to your component.
import React from 'react';
import _ from 'lodash';
class Container extends React.Component {
constructor(props) {
super(props);
this.state = { props: props.routerProps };
}
onLoadData(props) {
var mergedProps = _.merge(this.state.props, props);
this.setState({ props: mergedProps });
}
render() {
var Component = this.props.component;
return <Component {...this.state.props} onLoadData={this.onLoadData.bind(this)} />;
}
}
Then from your loaded component, when you get your data back from the server, just fire this.props.onLoadData(data) and the data will be merged with your current props.
Read the Router.createElement Documentation

Resources