I want to use Recoil to share a state between to React Router Routes.
My setup looks something like this:
ReactDOM.render(
<React.StrictMode>
<RecoilRoot>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="/checkin" element={<Checkin />} />
<Route path="/checkin/confirm" element={<ConfirmCheckin />} />
</Routes>
</BrowserRouter>
</RecoilRoot>
</React.StrictMode>,
document.getElementById("root")
);
My atom setup looks like this:
const checkinItemsState = atom<InventoryItem[]>({
key: "checkinItemsState",
default: [],
});
In both components i use useRecoilState(checkinItemsState)
Now when i open localhost:3000/checkin it shows my state and i can modify it. When i then navigate to localhost:3000/checkin/confirm my state is just an empty array.
What am I missing here? is there something i misunderstand?
So fare I've tried to wrap my Routes with a component like:
const Index: React.FC<{ children: ReactNode }> = ({ children }) => {
useRecoilState(checkinItemsState);
return (
<>
<span>Hello World</span>
{children}
</>
);
};
I guessed maybe it was because the state was nowhere referenced when moving between urls. This does not work.
Any help would be appreciated.
Related
My application is using react-router-dom v5.3 and I'm having trouble routing from the root url of my application to a child component (called the "See All" Page) while also passing props down. Currently, my code just renders an empty page whenever I navigate to the child component.
RootRouter.js:
export default function RootRouter() {
return (
<BrowserRouter>
<Switch>
<Route
path="/"
exact
render={() => <HomeView />}
/>
</Switch>
</BrowserRouter>
);
}
Homeview.js:
function HomeView() {
const seeAllViewTitle = "some_title_here"
return (
<div>
<div>Some content here!</div>
<Link to={`/seeall/${seeAllViewTitle}`}}>
<Button/>
</Link>
<Route path={`/seeall/${seeAllViewTitle}`}>
<SeeAllView
groupTitle={""}
pageData={[]}
eventHandler={some_function_here}
/>
</Route>
</div>
);
}
If I were to put the Route that is currently in homeview.js inside of Rootrouter.js, the component shows up, but I can't pass any props into it from there.
Issue
The HomeView component is rendered only when the path is exactly "/". When the link is clicked and navigates to "/seeall/some_title_here " the path no longer exactly matches and the HomeView component unmounts.
Solution
Remove the exact prop from the root route so nested/sub routes can also be matched and rendered.
export default function RootRouter() {
return (
<BrowserRouter>
<Switch>
<Route path="/" component={HomeView} />
</Switch>
</BrowserRouter>
);
}
If you did not intend for these components to be rendered at the same time then move the nested route out to the RootRouter component.
export default function RootRouter() {
return (
<BrowserRouter>
<Switch>
<Route path="/seeall/:title">
<SeeAllView
groupTitle={""}
pageData={[]}
eventHandler={some_function_here}
/>
</Route>
<Route path="/" component={HomeView} />
</Switch>
</BrowserRouter>
);
}
...
function HomeView() {
const seeAllViewTitle = "some_title_here"
return (
<div>
<div>Some content here!</div>
<Link to={`/seeall/${seeAllViewTitle}`}}>
<Button/>
</Link>
</div>
);
}
Are you remembering to receive props in the function declaration for HomeView? usually, you'll need to explicitly define that you are receiving props, either with a props variable or by defining specific prop names in an object syntax
Looking through old tutorials it seems like React Router v5 had support for sharing state across different routes using the context API but I can't find anything on a similar usage for Router v6.
React Router v5 Implementation of what I am trying to do:
const [user, setUser] = useState(null);
return (
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about/">About</Link>
</li>
</ul>
</nav>
<UserContext.Provider value={user,setUser}>
<Route path="/" exact component={Index} />
<Route path="/about/" component={About} />
</UserContext.Provider>
</div>
</Router>
);
and then you can access the state using the useContext hook
const {value, setValue} = useContext(UserContext);
v6 Implementation
When attempting to use this implementation with v6 (exchanging the degraded v5 components for the new v6 ones) you will run into errors because you can only have <Route> components as children in a router.
Is it possible to share state with a global context store across React Router V6 routes?
Below is my attempt at V6 implementation:
index.js
import { BrowserRouter as Router } from "react-router-dom";
ReactDOM.render(
<Router>
<App />
</Router>,
document.getElementById("root")
);
App.js
const [value, setValue] = useState("initial state");
return (
<>
<Header props={(key, setKey)} />
<DataContext.Provider value={(value, setValue)}>
<Route path="/" element={<Dashboard />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/configuration" element={<Configuration />} />
</DataContext.Provider>
</>
);
App.js different Approach
const [value, setValue] = useState("initial state");
return (
<DataContext.Provider value={(value, setValue)}>
<Header props={(key, setKey)} />
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/configuration" element={<Configuration />} />
</Routes>
</DataContext.Provider>
);
}
The issue with this solution is the state is still not updated globally when changed in one of the routes.
For example in /dashboard if I change value using setValue then it will reflect the changes in /dashboard but if I navigate to /configuration or refresh value will have reverted to original value, "initial state" in this case. (This is also the effect if I make a function in App.js which will use setValue and I pass the function to Provider instead)
I am sure that I could use React Redux to solve this problem but its really just one or two pieces of data that I need shared between routes - seems like overkill to implement all of the required redux boilerplate etc. and seems like a common use case that context should support.
Its simple you need to use Router inside your DataProvider.
index.js
// import { BrowserRouter as Router } from "react-router-dom";
ReactDOM.render(
<App />,
document.getElementById("root")
);
app.js
import { BrowserRouter as Router } from "react-router-dom";
// .....
// .....
// .....
const [value, setValue] = useState("initial state");
return (
<DataContext.Provider value={(value, setValue)}>
<Header props={(key, setKey)} />
<Router>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/configuration" element={<Configuration />} />
</Routes>
</Router>
</DataContext.Provider>
);
}
Also make sure you are using the Link of router to navigate to different pages. Using any other thing 'a' tag, etc. that refreshes the page will reset the context.
ISSUE
any route written manually in URL or triggered by button renders only index page (route name is changing, content of the page is always content of index)
App.js (handling all the main functions from the app)
// Exporting my router and passing all states/functions from App
<div className="App">
<Routes
........
........
........
onRemove={onRemove}
setCartOpen={setCartOpen}
cartOpen={cartOpen}
.......
.......
/>
</div>
Router (example of how my routes work)
export default function Routes({....., ....., onRemove,setCartOpen,cartOpen,..... })
<Router>
<Header
countCartItems={cartItems.length}
cartItems={cartItems}
onAdd={onAdd}
onRemove={onRemove}
setCartOpen={setCartOpen}
/>
<Suspense fallback={<Spinner />}>
<Switch>
<Route
path={baseRouteUrl + '/'}
component={(props) => (
<IndexPage
{...props}
.......
.......
onRemove={onRemove}
setCartOpen={setCartOpen}
cartOpen={cartOpen}
.......
.......
/>
)}>
</Route>
<Route
path={baseRouteUrl + '/about'}
component={About}
></Route>
<Route
path={baseRouteUrl + ''}
component={NotFound}
></Route>
.......
And Index.js
ReactDOM.render(
<React.StrictMode>
<QueryClientProvider client={client}>
<ChakraProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</ChakraProvider>
</QueryClientProvider>
</React.StrictMode>,
document.getElementById('root')
);
NOTE
Before trying to pass props in the routes the router was working just fine
The reason I decided to structure my app this way is because I need my Header to be exported on all pages with all the functionalities.
Is there anything I should do in a different way? I know it might be a little messy, so I'll be grateful to learn how to handle it cleaner and most important, how to make it work so all routes will render the correct pages.
Just like in most languages, a switch will match and runs the first thing it runs into.
In this case, it's matching your default path and rendering that one first.
One of the way to deal with this is to set exact to true
<Route exact path={...}... />
react router does partial matching so '/' is partial from '/about' when using exact it will disable the partial matching and will get the correct path
<Route
exact
path={baseRouteUrl + '/'}
component={(props) => (
<IndexPage
{...props}
.......
.......
onRemove={onRemove}
setCartOpen={setCartOpen}
cartOpen={cartOpen}
.......
.......
/>
)}>
</Route>
I was following a CRUD tutorial that used nested Routes like the code below. I tried to omit most of the code that doesn't concern routing.
After looking up several other tutorials on nested routing I noticed they don't use exported components like I was. I also noticed that the tutorial code below exported its components using withRouter.
index.js:
...imports
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root'),
);
App.js:
const App = ({ classes }) => (
<CssBaseline />
<AppHeader />
<main className={classes.main}>
<Home />
<Route exact path="/" component={Home} />
<Route exact path="/posts" component={PostManager} /> //This renders a list of posts
</main>
</Fragment>
PostsManager.js:
...imports
...constructor and other functions (this.savePost)
renderPostEditor = ({ match: { params: { id } } }) => {
if (this.state.loading) return null;
const post = find(this.state.posts, { id: Number(id) });
if (!post && id !== 'new') return <Redirect to="/posts" />;
return <PostEditor post={post} onSave={this.savePost} />;
};
render() { //component render funciton
...
<Button
variant="fab"
color="secondary"
aria-label="add"
className={classes.fab}
component={Link}
to="/posts/new"
>
<Route exact path="/posts/:id" render={this.renderPostEditor} />
}
...exporting component withRouter()
The problem I got was that when I tried to access /posts/new or /posts/2 which both should match /posts/:id, I didn't get any match. The method this.renderPostEditor obviously didn't get called and PostEditor wasn't rendered.
I tried to solve the problem by removing the Route in PostsManager.js and putting in App.js. That way I got a match but it didn't render the way I wanted because this.renderPostEditor dependended on PostManager.js
My question is why I didn't get a match inside PostsManager.js but got match in App.js?
Try removing the exact prop from <Route ... /> definition.
I'm starting new app using ReactJS. All the web env is quite new for me.
Anyway, I created an simple App, using react-router. Works perfectly :).
I tried to add Redux, and ... fail. I have no idea how use React-redux AND react-router.
Here's my input JS : index.js
class App extends React.Component{
render(){
return(
<div>
{routes}
</div>
);
}
}
ReactDOM.render(
<Provider store={store}> <App /> </Provider>,
document.getElementById('app')
);
Here's my route.js
export default (
<Router history={hashHistory}>
<Route path='/' component={Main}>
<IndexRoute component={Home} />
<Route path='playerOne' header="PlayerOne" component={PromptContainer} />
<Route path='playerTwo/:playerOne' header="PlayerTwo" component={PromptContainer} />
<Route path='battle' component={ConfirmBattleContainer} />
<Route path='results' component={ResultsContainer} />
<Route path='maninthemiddle' component={ManinthemiddleContainer} />
<Route path='button' component={ButtonContainer} />
</Route>
</Router>
);
and ofc my reducer.js
import reducer from './reducer.js'
import { createStore } from 'redux';
const store = createStore(reducer);
export default store;
And this is my error.
Uncaught Error: React.Children.only expected to receive a single React element > child
and my warning :
Warning: Failed prop type: Invalid prop children of type array supplied > > to Provider, expected a single ReactElement.
I know that Provider have to be on "top" of the app, but it's exactly what I did. I'm lost :(
Thanks for your helps guys.
You need to remove the whitespace around <App />. This:
ReactDOM.render(
<Provider store={store}> <App /> </Provider>,
document.getElementById('app')
);
should be:
ReactDOM.render(
<Provider store={store}><App /></Provider>,
document.getElementById('app')
);
the spaces in there are being treated as text nodes, i.e. additional children.