How to use context provider with react router components? - reactjs

I have browsed almost every resource on the internet and have not found a solution so please don't recommend me someone else's question that is identical because I have most likely already tried it.
Here is my container that has all of the routes. These reside in the App component which is basically the uppermost component.
Here is my provider. Basically, I have a title and when you click on the different links that take you to different views (i.e., Home, Client, Company) I do something like...
const { setHeader } = useContext(HeaderContext);
useEffect(() => {
setHeader("Company Dashboard");
document.title = "Company Dashboard"
})
As you can see the "setHeader("Company Dashboard")" should set the header. The header is actually a separate component that rerenders if the HeaderContext updates.
The Provider works btw. This is because the Header component has uses this context to set the element. So if I change the empty string to "oeijsofiej" it will display "oeijsofiej" as the title BUT it does NOT change when you click on different tabs.
I have tried to surround all of the components in a but unfortunately, that does absolutely nothing. No errors thrown and the setContext doesn't do anything. I have tried other ways of formatting the element like
<Route path="/" exact render={() => <HeaderContextProvider><Home></HeaderCOntextProvider>}/>
Unfortunately, this also does nothing. I've tried other methods and get weird errors. Not sure what I'm doing wrong I've exhausted all of my resources. Please help.

Related

How to target specific components through Links in react-router?

I want to insert links throughout the content of a React app, where links trigger 'navigation' only within specific Components in the tree. Ideally I would use a react library to do this, rather than invent my own framework from scratch.
Existing approaches that I can find, such as react-router, seem to assume that every routed component should only be visible when a route path matches it, rather than routes being able to selectively send 'control' signals to matching components, while unmatching components should not be affected at all.
My intended application needs independent navigation within different panes, similar to the behaviour of a HTML Frameset ( see e.g. this JavaDoc single-page navigation - https://docs.oracle.com/javase/7/docs/api/ ) where specific links have a href (a route in React) but also a 'target' which indicates which pane needs to be affected by a given navigation.
I am aware I could write a bespoke React eventing pattern. For example I could pass hooks to make changes through tree state, with my own bespoke hash or history eventing in place to monitor clicks. Before I consider writing my own framework for this, I want to understand what other approaches there are and I think I must be overlooking something obvious.
I have put together a repository which simplifies the problem from a react-router point of view.
https://github.com/cefn/graphql-gist/tree/fde58e9cf5d321d1edf3b916da4bdd95b79751a1/react-router-frames
This app has 'Frames' with embedded links. However, every Frame's component in the React tree has to be switched out for another matching component (or none) when a Link is clicked. Ideally I should be able to add a 'target' attribute or otherwise specialise a Link or target so that only a targeted part of the tree is affected by a matching link.
It should be possible for example, to cause the color of the name='left' or name='right' frames to change independently in https://github.com/cefn/graphql-gist/blob/fde58e9cf5d321d1edf3b916da4bdd95b79751a1/react-router-frames/src/FrameSet.js
I am hoping for something from the mainstream react ecosystem which supports routing (e.g. well-documented components with hash listening, history support) but not where every Link affects every Route in the page.
Here is a solution which exploits react-router and isn't totally horrible.
function FrameSet(props) {
return <Router>
<FilterPath pathPrefix="/left/">
<View />
</FilterPath>
<FilterPath pathPrefix="/right/">
<View />
</FilterPath>
</Router>
}
A FilterPath always passes on its pathPrefix value to its childrens' props, and optionally, (when the react-router location matches the pathPrefix) passes on a pathSuffix too.
In this way, each View above only receives a pathSuffix update when a route path begins with their pathPrefix and hence 'targets' them.
A draft working implementation of FilterPath is...
function FilterPath(props) {
return <Route render={({ location: { pathname } }) => {
const { pathPrefix } = props
let pathSuffix
if (pathname.startsWith(pathPrefix)) {
pathSuffix = pathname.slice(pathPrefix.length)
}
return React.Children.map(props.children, child => React.cloneElement(child, { pathPrefix, pathSuffix }))
}} />
}
A working example using FilterPath can be seen at https://github.com/cefn/graphql-gist/blob/2de588be6c2d30b92d452f71749377c9dc6c19c7/react-router-frames/src/FrameSet.js#L22

How to override Back button when a drawer is showing in React 16 / Router V4

I have a structure like this, there's a route like:
<Route path="/sample-route" component={ComponentA}/>
Then ComponentA has:
<ComponentA>
<ComponentB/>
<ComponentC>
<MaterialUIDrawer/>
</ComponentC>
</ComponentA>
ComponentC is used in 5 different routes as a child. The MaterialUIDrawer is showing based on a flag in a redux reducer. The problem I'm trying to solve is when the drawer is open, clicking back hides it but also navigates back. I tried solving it like this:
window.onpopstate = (e) => {
if (this.props.isOpen) {
this.props.toggleDrawer(false);
this.props.history.replace(this.props.match.url);
}
};
This has 2 issues:
If this is the first page you land on, it doesn't actually do anything, the function doesn't trigger
If you are on a different site and navigate to the url that contains the drawer, hitting Back moves you to a different URL (different domain)
I also tried setting a <Route/> in ComponentC and then the drawer lives there, but I didn't manage to get it to work, maybe the path is wrong. It felt like this might be the right way though, if path is /path1, then drawer lives in /path1/drawer, or /path2/drawer, etc.
I'm trying to find a way for the Back button in a browser to close the drawer (so execute a function I define) and override the default functionality.
I think you should be using props here instead of paths, so something like /path1?drawer=1, but you will definitely need to use history/location so that the back button can actually go back, so you are on the right trail.
I'm not sure what you are using for a browser history manager, but I would recommend tying off of that instead of leaning on the window pop-state. Your history module should be the source of truth and feed redux, not the other-way around IMO.

Wait for Redux action to dispatch a second one

Hi and thanks in advance for reading my question.
I am working on a React app (version 16) with router v3 and Redux.
The app has an structure similar to this:
<Route path="/" component={BakeryComponent}>
<IndexRoute component={LandingComponent}/>
<Route path="employees" component={EmployeesComponent}/>
<Route path="clients" component={ClientComponent}/>
</Route>
So we have like different entities. Bakery which is the main one, employees that depends on the selected bakery and clients which also depends on the bakery.
When we access the app we land on the landing page which shows general info of the bakery (there is one by default selected) like name, employees quantity and some other. All that info comes from the backend, it is retrieved in the componentDidMount of either Bakery or Landing component (at this point it does not make any difference).
When the user is in the landing page he can navigates to either employees or clients views. When that happens the component is mounted, let's suppose it is employees component, and in the component's componentDidMount method we retrieve the needed information (the employees list in this case).
Everything works fine until now.
Now, if the user refreshes the page everything should still works (it is a requirement) but it doesn't. The problem is that when that happens BakeryComponent and EmployeesComponent (both of them) are mounted again, so new backends calls are triggered (in parallel). But the backend call to employees depends on bakery to be resolved first because we need to know the employees of what bakery we want, so we get an error at thi point.
I tried to check in the invoked action (the one to retrieve the employees) if the bakery was already resolve (using getState) and if not, wait for it to be resolved. But it does not convince me, it looks kind of a hack. I have the feeling that React/Redux should already have the tools to solve this but I just don't know them. Am I right?
Any help will be really helpful.
Thanks !
I can think of a way for you, although It's actually not a cool solution. You can check if the data in the BakeryComponent is ready or not before calling dispatch in the LandingComponent. Something like this:
BakeryComponent
componentDidMount() {
// after this one finish we may have something in the store like: state.bakery.loaded = true
dispatch(loadBakery())
}
LandingComponent
// Landing component should connect to state.bakery.loaded to get updated data
componentDidMount() {
initData()
}
componentDidUpdate(){
initData()
}
initData() {
// check if bakery loaded and landing data is not loaded
if (state.bakery.loaded && !state.landing.loaded) {
// this should load data and set the state.landing.loaded to true
dispatch(loadLanding())
}
}

Does React have keep-alive like Vue js?

I made a Todo list with React js. This web has List and Detail pages.
There is a list and 1 list has 10 items. When user scroll bottom, next page data will be loaded.
user click 40th item -> watch detail page (react-router) -> click back button
The main page scroll top of the page and get 1st page data again.
How to restore scroll position and datas without Ajax call?
When I used Vue js, i’ve used 'keep-alive' element.
Help me. Thank you :)
If you are working with react-router
Component can not be cached while going forward or back which lead to losing data and interaction while using Route
Component would be unmounted when Route was unmatched
After reading source code of Route we found that using children prop as a function could help to control rendering behavior.
Hiding instead of Removing would fix this issue.
I am already fixed it with my tools react-router-cache-route
Usage
Replace <Route> with <CacheRoute>
Replace <Switch> with <CacheSwitch>
If you want real <KeepAlive /> for React
I have my implementation react-activation
Online Demo
Usage
import KeepAlive, { AliveScope } from 'react-activation'
function App() {
const [show, setShow] = useState(true)
return (
<AliveScope>
<button onClick={() => setShow(show => !show)}>Toggle</button>
{show && (
<KeepAlive>
<Test />
</KeepAlive>
)}
</AliveScope>
)
}
The implementation principle is easy to say.
Because React will unload components that are in the intrinsic component hierarchy, we need to extract the components in <KeepAlive>, that is, their children props, and render them into a component that will not be unloaded.
Until now the awnser is no unfortunately. But there's a issue about it in React repository: https://github.com/facebook/react/issues/12039
keep-alive is really nice. Generally, if you want to preserve state, you look at using a Flux (Redux lib) design pattern to store your data in a global store. You can even add this to a single component use case and not use it anywhere else if you wish.
If you need to keep the component around you can look at hoisting the component up and adding a "display: none" style to the component there. This will preserve the Node and thus the component state along with it.
Worth noting also is the "key" field helps the React engine figure out what tree should be unmounted and what should be kept. If you have the same component and want to preserve its state across multiple usages, maintain the key value. Conversely, if you want to ensure an unmount, just change the key value.
While searching for the same, I found this library, which is said to be doing the same. Have not used though - https://www.npmjs.com/package/react-keep-alive

React-Router v4 (List / ListItem, Routes, Animations)

I just ran into a problem with react router v4, maybe it's not a big deal, but I don't know how to handle this the right way.
It should look something like this:
<Route path="/" component={List}>
<Route path="/listitem/:id" component={ListItem} />
</Route>
If I am now on the ("/" - List)-page I want to see all the ListItems from the different ("/listitem/:id" - ListItem)-pages. (with less informations but this should be a simple css-problem)
As soon as I click at one, I want to turn that one fullscreen (animations and stuff... but that's not the problem here) and the route should change, unsurprisingly :-).
If I am now on one specific ListItem and trigger history.goBack(), I want to animate that Item back into the List and the route should change back to "/".
Additional (maybe optional) thought: If I visite the site initially at a "/listitem/:id"-page and click on a Link to the "(/" - List)-page, there shouldn't be any animation of the ListItem.
I guess it's a basic problem for react-router, but I actually don't know right now how to implement this properly.
I don't know if it makes any difference, but I am using Redux aswell.
I hope, someone can help me out.
Thank you in advance.
I think this would be best handled with state in the Parent component. For example:
this.state = {
fullscreen: false,
idOpened: null
}
Once you click on the child component (the list item), you set a callback that sends the ID to the parent which then triggers the fullscreen-mode. This way you won't need to use the history API and the visitors won't have to wait for page loads (except for the initial one).
It'll also make it easy to not trigger any animations when clicking on another list item.

Resources