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.
Related
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.
I'm learning React and came across Context API, while looking at sample code I found it has got render () function
Per my understanding Context react class component is there to manage state, so it's not clear why do we need to use render( ) function ?
Class components have render methods. Class components can access the Context API but the render method is completely decoupled from the Context API.
Summary:
1) Do you know how to keep the state of a Context Provider present when it is mounted/unmounted through routing?
2) Or do you know a well maintained Flux implementation that supports multiple separated stores?
In detail:
Besides React components own state I've been using mostly redux so far. Besides not loving the idea of having every state managed globally, even though it might only be relevant for a subtree, it also becomes an issue for my new project.
We want to dynamically load components and add them via routing to the app. To be able to have components ready for plug and play, we want them to take care of their own state (store it, request it from the server, provide a strategy to modify it).
I read about how to dynamically add reducers to the global store with redux, but I actually find the approach of Reacts Context API much nicer where I can encapsulate some state in a Provider and can consume it wherever I need it.
The only issue I have is, that a Provider and a Consumer are React components, so if they are part of a component, that is mounted and unmounted through routing, the state that might have been created or fetched once, is gone.
It seems to me that there is no way to solve that, besides temporarily storing the state in the localstorage or on the server. If there is, please let me know!!!
If there shouldn't be a better solution:
I also thought about a more original Flux implementation that would allow multiple stores, which could be encapsulated with the relavant component subtree. But I haven't really found any well maintained Flux implementation besides Redux. Mobx being the exception, but I really prefer the reducer solution of Redux over the observable solution of Mobx. So again, if you know a multi store Flux implementation that is well maintained, please let me know!!!
I would be really happy about some feedback and hope you can point me into a direction that is more satisfiying than dynamic reducer Redux or temporarily persisted Context state.
Thanks a lot in advance!
Sorry that it's quite a late answer
Are you using React Router?
The state should be persisted and it shouldn't clear if you are navigating correctly. There should be no page reload as this will cause the state to clear.
Here is an example:
import { Router as RootRouter } from 'react-router-dom';
import Router from './routes/Router';
const App = () => {
return (
<MyContext value={useReducer(myReducer, initialState)}>
<RootRouter history={browserHistory}>
<Router />
</RootRouter>
</AuthContext>
);
}
import About from '../components/About';
const Router = () => {
return (
<Switch>
<Route exact path='/about' component={About}></Route>
</Switch>
}
On your main home component, you have to use a Link or Navlink to "switch" between components. Therefore, you'll have something like...
import { Link } from 'react-router-dom';
<Link to="/about">About</Link>
This will navigate you to the about page in which you can still access the context stage where nothing is cleared.
So I figured out a way to work around the problem with Context (first question): I store the state of the Provider component in a variable. That way, when that component is mounted again, it uses the "persisted" state as the initial value for it's state.
let persistedState = {};
const Context = React.createContext();
export class Provider extends React.PureComponent {
state = { ...persistedState };
updateState = (nextState) => this.setState(nextState, () => {
persistedState = {...this.state};
});
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
Upgrading my application to React Router v4 I'm struggling to find a clean way to implement the old onUpdate logic which I previously used the hide a popup menu on navigation.
The only way I can see in the documentation is to take advantage of the route render method but it seems much more complicated than before - any easier solutions?
const HidePopupThenRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={matchProps => {
hidePopup();
return <Component {...matchProps}/>
}}/>
)
<HidePopupThenRoute path="/" component={MyComponent}/>
I wouldn't consider that too complicated, but I can understand it not looking right to make imperative calls inside of a render function.
One alternative would be to create an OnUpdate component that listens for location changes and calls some function when the happen.
You can see the source code for an implementation of this that I wrote. You can either use that or replicate your own component with similar functionality. Basically, all that it does is subscribe to the history object and calls whatever function you pass to it when the location changes.
const MyComponent = () => (
<div>
<OnUpdate call={hideProps} />
<h1>My Component</h1>
<p>...</p>
</div>
)
A relatively clean solution was suggested on GitHub. What it comes down to is that you read the Router's history from React's Context API, where it gets populated by React Router. You can do so by defining the contextTypes property in your component:
static contextTypes = {
router: PropTypes.object
};
That will make sure that the Router is attached to that component. You can then use that the access the router and its history from the context, and add a callback that gets executed on history changes:
this.context.router.history.listen(hidePopup)
You'll probably want to do that in componentDidMount.
I am doing something like:
<Router history={browserHistory}>{routes}</Router>
When I do above whenever URL in address bar changes call is going to server but this is not what I want, I want first time page to load from server but after that whenever route change component should load in client side only. Am I missing something here?
In client side I am doing something like :
ReactDOM.render(
<Provider store={app.store}>
<Router history={browserHistory}>{routes}</Router>
</Provider>,
document.getElementById("app")
);
and my routes look like:
const routes = (
<Route path="/" component={DJSAppContainer}>
<Route path="page" component={DJSPage}>
<Route path="/page/:pageName" component={PageContainer} />
</Route>
</Route>
);
Now whenever I do location.href = "/page/xyz" it goes to server and load the content.
You shouldn't change location.href directly. You should send the new path to React using:
ReactRouter.browserHistory.push(newPath);
If you have anchor tags, you should use the <Link> component mentioned in #ahutch's answer.
React router exposes as props the history used in the current views. See their docs for all the props that are injected here.
If you want to redirect from DJSAppContainer or any of the two children views DJSPage or PageContainer you could do it by accessing the history property:
const {history} = this.props;
history.pushState('/path/to/new/state');
If on the other hand you do some fancy stuff and want to redirect from outside the components, try this (taken from react router docs)
// somewhere like a redux/flux action file:
import { browserHistory } from 'react-router'
browserHistory.push('/some/path')
This two approaches assume you are trying to redirect based on some complex logic, not as a consequence of a click event. If you just want to handle click events, try using the <Link/> component of react-router.
It seems that the history property is now deprecated, and seems to be replaced by the router context property as seen in the implementation of the link component. Basically, if you really want your code to be future proof, you should ask for the contextType:
contextTypes: {
router: object
},
And then use it from the context of the component:
this.context.router.push('/some/path')
Assuming you're using node on the back-end, make sure that you have it set up according to the react-router docs.
// send all requests to index.html so browserHistory in React Router works
app.get('*', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'))
})
Also, when you're linking between components and you want to ensure that you are using react-router for the routing instead of the server, make sure to use Link. Hope that answers your question.
I was having a similar issue, while hashHistory worked without issue browserHistory would always load from the server. I got things working by remembering to call preventDefault(). Here is my function:
handleSubmit: function (e) {
e.preventDefault();
var username = this.usernameRef.value;
this.usernameRef.value = '';
this.context.router.push(`/profile/${username}/`);
}
handleSubmit is called on a form onSubmit.