Prompt is made when navigating within same route - reactjs

For the use case when a page has an internal route, the prompt triggers even when navigating to an internal route where a prompt might not be necessary, see code example. Is there a way to disable the prompt when navigating to known safe routes?
import React, {Component} from 'react';
import {BrowserRouter, Route, Switch, Link, Prompt} from 'react-router-dom';
export default class App extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route component={About} path={"/about"}/>
<Route component={Home} path={"/"}/>
</Switch>
</BrowserRouter>
);
}
}
class Home extends Component {
constructor(props) {
super(props);
this.state = {input: 'hello?'}
}
render() {
return (
<div>
<h1>Home</h1>
<input value={this.state.input}
onChange={(e) => this.setState({input: e.target.value})}/><br />
<Link to={"/info"}>More Info</Link><br />
<Link to={"/about"}>About Page</Link><br />
{/*Triggers even when going to /info which is unnecessary*/}
<Prompt message="Move away?" when={this.state.input !== 'hello?'}/>
<Route path={"/info"} component={Info}/>
</div>
);
}
}
class About extends Component {
render() {
return (
<h1>About page</h1>
);
}
}
class Info extends Component {
render() {
return (
<p>Here be some more info</p>
);
}
}
In the example above, About is a different page and so should trigger when the input has changed, which it does correctly. But the /info route is an internal route for Home, so the prompt is unnecessary, the internal state of Home is preserved after navigation so nothing is lost.
The actual use case here is for a modal to be shown when the route is active, but that is mostly CSS stuff so I excluded it from the example.

I think a callback function as a message prop is what are you looking for. You can try to give a callback function to Prompt's message prop. That callback will be called with an object as an argument which will have all the information about the next route.
This is what documentation says about it:
message: func
Will be called with the next location and action the
user is attempting to navigate to. Return a string to show a prompt to
the user or true to allow the transition.
The object has a pathname attribute which is the next path by checking it you can figure out if the path is safe. Here is what I'm talking about:
<Prompt message={(params) =>
params.pathname == '/about' ? "Move away?" : true } />
And here is the pen with working code which I've created from your examples.
Hope it helps.

Related

ReactJS -How to create multistep component/form with single path using React Router

I want to switch between components after the user entered the requested info.
Components that will be shown to user by this order:
{MobileNum } Enter mobile number
{IdNumber } ID number
{CreatePassword } Create Password
When all these steps are completed the browser will switch to the home page.
The user must not be able to move between pages until he filled each request in each component.
Now I want a better way with router as if I had 3-4 components inside Login, and must be in a secured whey, also the user must not be able to switch components manually through the URL.
import React, { Component } from 'react';
import {
BrowserRouter as Router,
Redirect,
Route,
Switch,
} from 'react-router-dom';
import MobileNum from './MobileNum.jsx';
import IdNumber from './IdNum.jsx';
import CreatePassword from './createPassword .jsx';
class SignUp extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Router>
<Switch>
//// Here needs to know how to navigate to each component on its turn
<Route path='/' component={MobileNum} />
<Route path='/' component={IdNumber} />
<Route path='/' component={CreatePassword } />
</Switch>
</Router>
</div>
);
}
}
export default SignUp ;
I searched the web in reactrouter.com and many others as here for a clean solution but found no answer.
Any Idea what's the best way to do it ?
Thanks
Since router variable like location are immutable, conditional rendering itself would be better option, you can try switch if you don't want to use if else.
I have given an example below, you have to fire that afterSubmit when values are submitted in each component .If you use redux, you could implement it better as you can store the value in redux state and set it directly from each component using dipatch.
//App.js
import React, { useState } from 'react';
import MobileNum from './MobileNum.jsx';
import IdNumber from './IdNum.jsx';
import CreatePassword from './createPassword .jsx';
function App (){
const [stage,setStage]= useState(1);
switch(stage){
case 2:
return <IdNumber afterSubmit={setStage.bind(null,3)}/>
break;
case 3:
return <CreatePassword afterSubmit={setStage.bind(null,4)} />
case 4:
return <Home />
break;
default:
return <MobileNum afterSubmit={setStage.bind(null,2)}/>
}
}
export default App;
//Root
import React, { Component } from 'react';
import App from './App.jsx';
import {
BrowserRouter as Router,
Redirect,
Route,
Switch,
} from 'react-router-dom';
class Login extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Router>
<Switch>
<Route path='/' component={App} />
</Switch>
</Router>
</div>
);
}
}
//Add on - Sign up form class based
class SignUp extends React.Component {
constructor(props) {
super(props);
this.state = { stage: 1 };
}
render() {
switch (this.state.stage) {
case 2:
return <IdNumber afterSubmit={() => this.setState({ stage: 3 })} />;
break;
case 3:
return <CreatePassword afterSubmit={() => this.setState({ stage: 4 })} />;
case 4:
return <Home />;
break;
default:
return <MobileNum afterSubmit={() => this.setState({ stage: 2 })} />;
}
}
}
It will take special handling in React Router to meet your security requirements. I personally would load the multi-step wizard on one URL rather than changing the URL for each step as this simplifies things and avoids a lot of potential issues. You can get the setup that you want, but it is much more difficult than it needs to be.
Path-Based Routing
I am using the new React Router v6 alpha for this answer, as it makes nested routes much easier. I am using /signup as the path to our form and URLs like /signup/password for the individual steps.
Your main app routing might look something like this:
import { Suspense, lazy } from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Header from "./Header";
import Footer from "./Footer";
const Home = lazy(() => import("./Home"));
const MultiStepForm = lazy(() => import("./MultiStepForm/index"));
export default function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<BrowserRouter>
<Header />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/signup/*" element={<MultiStepForm/>} />
</Routes>
<Footer />
</BrowserRouter>
</Suspense>
);
}
You'll handle the individual step paths inside the MultiStepForm component. You can share certain parts of the form across all steps. The part which is your Routes should just be the part that is different, ie. the form fields.
Your nested Routes object inside the MultiStepForm is essentially this:
<Routes>
<Route path="/" element={<Mobile />} />
<Route path="username" element={<Username />} />
<Route path="password" element={<Password />} />
</Routes>
But we are going to need to know the order of our route paths in order to handle "Previous" and "Next" links. So in my opinion it makes more sense to generate the routes based on a configuration array. In React Router v5 you would pass your config as props to a <Route/>. In v6 you can skip that step and use object-based routing.
import React, { lazy } from "react";
const Mobile = lazy(() => import("./Mobile"));
const Username = lazy(() => import("./Username"));
const Password = lazy(() => import("./Password"));
/**
* The steps in the correct order.
* Contains the slug for the URL and the render component.
*/
export const stepOrder = [
{
path: "",
element: <Mobile />
},
{
path: "username",
element: <Username />
},
{
path: "password",
element: <Password />
}
];
// derived path order is just a helper
const pathOrder = stepOrder.map((o) => o.path);
Note that these components are called with no props. I am assuming that they get all of the information that they need through contexts. If you want to pass down props then you will need to refactor this.
import { useRoutes } from "react-router-dom";
import { stepOrder } from "./order";
import Progress from "./Progress";
export default function MultiStepForm() {
const stepElement = useRoutes(stepOrder);
return (
<div>
<Progress />
{stepElement}
</div>
);
}
Current Position
This is the part where things start to become convoluted. It seems that useRouteMatch has been removed in v6 (for now at least).
We can access the matched wildcard portion on the URL using the "*" property on the useParams hook. But this feels like it might be a bug rather than an intentional behavior, so I'm concerned that it could change in a future release. Keep that in mind. But it does work currently.
We can do this inside of a custom hook so that we can derive other useful information.
export const useCurrentPosition = () => {
// access slug from the URL and find its step number
const urlSlug = useParams()["*"]?.toLowerCase();
// note: will be -1 if slug is invalid, so replace with 0
const index = urlSlug ? pathOrder.indexOf(urlSlug) || 0 : 0;
const slug = pathOrder[index];
// prev and next might be undefined, depending on the index
const previousSlug = pathOrder[index - 1];
const nextSlug = pathOrder[index + 1];
return {
slug,
index,
isFirst: previousSlug === undefined,
isLast: nextSlug === undefined,
previousSlug,
nextSlug
};
};
Next Step
The user must not be able to move between pages until he filled each request in each component.
You will need some sort of form validation. You could wait to validate until the user clicks the "Next" button, but most modern websites choose to validate the data every time that the form changes. Packages like Formik and Yup are a huge help with this. Check out the examples in the Formik Validation docs.
You will have an isValid boolean which tells you when the user is allowed to move on. You can use that to set the disabled prop on the "Next" button. That button should have type="submit" so that its clicks can be handled by the onSubmit action of the form.
We can make that logic into a PrevNextLinks component which we can use in each form. This component uses the formik context so it must be rendered inside of a <Formik/> form.
We can use the info from our useCurrentPosition hook to render a Link to the previous step.
import { useFormikContext } from "formik";
import { Link } from "react-router-dom";
import { useCurrentPosition } from "./order";
/**
* Needs to be rendered inside of a Formik component.
*/
export default function PrevNextLinks() {
const { isValid } = useFormikContext();
const { isFirst, isLast, previousSlug } = useCurrentPosition();
return (
<div>
{/* button links to the previous step, if exists */}
{isFirst || (
<Link to={`/form/${previousSlug}`}>
<button>Previous</button>
</Link>
)}
{/* button to next step -- submit action on the form handles the action */}
<button type="submit" disabled={!isValid}>
{isLast ? "Submit" : "Next"}
</button>
</div>
);
}
Here's an example of how one step might look:
import { Formik, Form, Field, ErrorMessage } from "formik";
import React from "react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router";
import Yup from "yup";
import "yup-phone";
import PrevNextLinks from "./PrevNextLinks";
import { useCurrentPosition } from "./order";
import { saveStep } from "../../store/slice";
const MobileSchema = Yup.object().shape({
number: Yup.string()
.min(10)
.phone("US", true)
.required("A valid US phone number is required")
});
export default function MobileForm() {
const { index, nextSlug, isLast } = useCurrentPosition();
const dispatch = useDispatch();
const navigate = useNavigate();
return (
<div>
<h1>Signup</h1>
<Formik
initialValues={{
number: ""
}}
validationSchema={MobileSchema}
validateOnMount={true}
onSubmit={(values) => {
// I'm iffy on this part. The dispatch and the navigate will occur simoultaneously,
// so you should not assume that the dispatch is finished before the target page is loaded.
dispatch(saveStep({ values, index }));
navigate(isLast ? "/" : `/signup/${nextSlug}`);
}}
>
{({ errors, touched }) => (
<Form>
<label>
Mobile Number
<Field name="number" type="tel" />
</label>
<ErrorMessage name="number" />
<PrevNextLinks />
</Form>
)}
</Formik>
</div>
);
}
Preventing Access via URL
The user must not be able to switch components manually through the URL.
We need to redirect the user if they attempt to access a page which they are not permitted to view. The Redirects (Auth) example in the docs should give you some ideas on how this is implemented. This PrivateRoute component in particular:
// A wrapper for <Route> that redirects to the login
// screen if you're not yet authenticated.
function PrivateRoute({ children, ...rest }) {
let auth = useAuth();
return (
<Route
{...rest}
render={({ location }) =>
auth.user ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
);
}
But what is your equivalent version of useAuth?
Idea: Look at the Current Progress
We could allow the visitor to view their current step and any previously entered steps. We look to see if the user is allowed to view the step which they are attempting to access. If yes, we load that content. If no, you can redirect them to their correct step or to the first step.
You would need to know what progress has been completed. That information needs to exist somewhere higher-up in the chain like localStorage, a parent component, Redux, a context provider, etc. Which you choose is up to you and there will be some differences. For example using localStorage will persist a partially-completed form while the others will not.
Where you store is less important that What you store. We want to allow backwards navigation to previous steps and forwards navigation if going to a previously-visited step. So we need to know which steps we can access and which we can't. The order matters so we want some sort of array. We would figure out the maximum step which we are allowed to access and compare that to the requested step.
Your component might look like this:
import { useRoutes, Navigate } from "react-router-dom";
import { useSelector } from "../../store";
import { stepOrder, useCurrentPosition } from "./order";
import Progress from "./Progress";
export default function MultiStepForm() {
const stepElement = useRoutes(stepOrder);
// attempting to access step
const { index } = useCurrentPosition();
// check that we have data for all previous steps
const submittedStepData = useSelector((state) => state.multiStepForm);
const canAccess = submittedStepData.length >= index;
// redirect to first step
if (!canAccess) {
return <Navigate to="" replace />;
}
// or load the requested step
return (
<div>
<Progress />
{stepElement}
</div>
);
}
CodeSandbox Link. (Note: Most of the code in the three step forms can and should be combined).
This is all getting rather complicated, so let's try something simpler.
Idea: Require that the URL Be Accessed from a Previous/Next Link
We can use the state property of a location to pass through some sort of information that lets us know that we've come from the correct place. Like {fromForm: true}. Your MultiStepForm can redirect all traffic that lacks this property to the first step.
const {state} = useLocation();
if ( ! state?.fromForm ) {
return <Navigate to="" replace state={{fromForm: true}}/>
}
You would make sure that all of your Link and navigate actions inside of the form are passing this state.
<Link to={`/signup/${previousSlug}`} state={{fromForm: true}}>
<button>Previous</button>
</Link>
navigate(`/signup/${nextSlug}`, {state: { fromForm: true} });
With No Path Change
After having written quite a lot of code and explanation about authenticating a path, I've realized that you haven't explicitly said that the path needs to change.
I just need to use react-router-dom properties to navigate.
So you could make use of the state property on the location object to control the current step. You pass the state through your Link and navigate the same as above, but with an object like {step: 1} instead of {fromForm: true}.
<Link to="" replace state={{step: 2}}>Next</Link>
You can majorly simplify your code by doing this. Though we come back to a fundamental question of why. Why use React Router if the important information is a state? Why not just use a local component state (or Redux state) and call setState when you click on the "Next" button?
Here's a good article with a fully-implemented code example using local state and the Material UI Stepper component:
Build a multi-step form with React Hooks, Formik, Yup and MaterialUI by Vu Nguyen
CodeSandbox
When all these steps are completed the browser will switch to the home page.
There are two ways to handle your final redirect to the home page. You can conditionally render a <Navigate/> component (<Redirect/> in v5) or you can call navigate() (history.push() in v5) in response to a user action. Both versions are explained in detail in the React Router v6 Migration guide.
I don't think adding React Router or any library changes the way how we solve a problem in React.
Your earlier approach was fine. You could wrap all it in a new component, like,
class MultiStepForm extends React.Component {
constructor(props) {
super(props);
this.state = {
askMobile: true,
};
};
askIdentification = (passed) => {
if (passed) {
this.setState({ askMobile: false });
}
};
render() {
return (
<div className='App-div'>
<Header />
{this.state.askMobile ? (
<MobileNum legit={this.askIdentification} />
) : (
<IdNumber />
)}
</div>
);
}
}
Then use this component on your Route.
...
<Switch>
<Route path='/' component={MultiStepForm} />
// <Route path='/' component={MobileNum} />
// <Route path='/' component={IdNumber} />
// <Route path='/' component={CreatePassword } />
</Switch>
...
Now how you'd like to move on with this is a completely new question.
Also, I have corrected the spelling of askIdentification.

Prevent remounting of expensive (class) component with shouldComponentUpdate() (List of images, inside router)

I'm rendering a very large list of images that, when clicked individually, are added to a "viewer" div. The problem is that each time I add an image to the viewer, the original list re-renders, even though no changes have been made to the list's content.
I've tried using shouldComponentUpdate() at every level, as well as using React.memo. Neither appear to have any effect. I've also looked in to whether the time should be spent making the components functional and researching hooks (useContext() looks enticing), but I'm too new at React to know if that would just be more time wasted. (Please feel free weigh in on whether this is a waste of time.)
I don't know where the problem is, so I'm not sure a snippet would do much good. Instead, I've stripped down the problem to its bones and posted a sandbox version here
https://codesandbox.io/s/async-darkness-l920b
At the moment, my shouldComponentUpdate comparison is pretty straightforward for each class; something like:
if (nextProps.photoData === this.props.photoData) {
return false;
} else {
return true;
}
If you open the codesandbox console you'll see I'm logging Year.js > <ImageList /> is rendering to flag each successive render of the list in question.
Any help, even a nudge in the right direction, would be hugely appreciated. I've been reading blog articles for a solid day now and nothing seems to help.
That's because the PhotoView in App.js is defined inside render method, so when state update causing the render, then the PhotoView redefined again. It's a new component every time for The App component.
Please define components outside the render function:
import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Year from "./Year";
import Viewer from "./Viewer";
import dataObj from "./dataObj.json";
import "./App.css";
class App extends React.Component {
constructor(props) {
super(props);
this.PhotoView = this.PhotoView.bind(this)
this.state = {
current: {
year: 2019,
url: ""
},
viewerData: [],
photoData: null
};
}
componentDidMount() {
this.setState({
photoData: dataObj
});
}
addToViewer = moment =>
this.setState(state => {
const viewerData = state.viewerData.concat(moment.props.data);
return {
viewerData,
value: ""
};
});
About() {
return (
<div>
<h1>About</h1>
</div>
);
};
PhotoView(url) {
return (
<div className="PhotoView">
<Year
setCurrent={this.setCurrent}
photoData={this.state.photoData}
addToViewer={this.addToViewer}
/>
<Viewer
viewerData={this.state.viewerData}
setCurrent={this.setCurrent}
/>
</div>
);
}
render() {
return (
<div className="App">
<Router>
<nav>
<Link to="/about">About</Link>
<Link to="/">Photo View</Link>
</nav>
<Switch>
<Route path="/about" exact component={this.About} />
<Route path="/" component={this.PhotoView} />
</Switch>
</Router>
</div>
);
}
}
export default App;
Or move them to individual files.

React call parent method from child of a child

i'm facing a small problem with my react app.
I'm using bluprintjs Toaster, and i need to display them on top of all other component, no matter what. Like this if there is an error during login or logout the user will always see the toast even if there is a redirection.
My problem is, that i have a middle component that is used to protect access to unAuthenticated user.
On my app class i have a ref to the Toaster and can easily call renderToaster to display a toast. So the method is working correctly.
But when i pass it to my ProtectedRoute and then to MyForm Component i can't call it in the MyFrom component.
From App -> ProtectedRoute -> MyForm if i print this.props i can see the renderToaster() Method, but i think the link from MyFrom -> ProtectedRoute -> App is somehow broken because on MyFrom i have the this.toaster is undefined error.
How can i call my parent parent method. Or how can i create a link between app and MyForm compenent passing through ProtectedRoute?
Thank you for your help.
My App class:
class App extends Component {
renderToaster(intent, message) {
this.toaster.show({
intent: intent,
message: message
});
}
<React.Fragment>
<NavBarComponent />
<Switch>
<ProtectedRoute
exact
path="/path1"
name="path1"
location="/path1"
renderToaster={this.renderToaster}
component={MyForm}
/>
<ProtectedRoute
exact
path="/path2"
name="path2"
location="/path2"
component={params => <MyForm {...params} renderToaster={this.renderToaster} />}
/>
</Switch>
<Toaster
position={Position.BOTTOM}
ref={element => {
this.toaster = element;
}}
/>
</React.Fragment>
}
My ProtectedRoute class:
import React, { Component } from 'react';
import { Route, Redirect } from 'react-router-dom';
import { AuthContext } from '../providers/AuthProvider';
class ProtectedRoute extends Component {
state = {};
render() {
const { component, ...rest } = this.props;
const Component = component;
return (
<AuthContext>
{({ user }) => {
return user ? (
<Route render={params => <Component {...params} {...rest} />} />
) : (
<Redirect to="/" />
);
}}
</AuthContext>
);
}
}
export default ProtectedRoute;
And on my last class (MyForm passed to the protected Route) i call my renderToaster Method like this:
/**
* Component did Mount
*/
componentDidMount() {
this.props.renderToaster(Intent.PRIMARY, 'helloo');
}
You either need to bind renderToaster in the class constructor:
constructor(){
this.renderToaser = this.renderToaster.bind(this);
}
or declare renderToaser as an ES7 class property.
renderToaster = (intent, message) => {
this.toaster.show({
intent: intent,
message: message
});
}
The problem is this in renderToaster isn't pointing where you think it is when the method is passed to the child component. If you use either of these methods, then this will refer back to the class.
See the official docs for more detail: https://reactjs.org/docs/handling-events.html

React Context API + withRouter - can we use them together?

I built a large application where a single button on the navbar opens a modal.
I'm keeping track of the modalOpen state using context API.
So, user clicks button on navbar. Modal Opens. Modal has container called QuoteCalculator.
QuoteCalculator looks as follows:
class QuoteCalculator extends React.Component {
static contextType = ModalContext;
// ...
onSubmit = () => {
// ...
this.context.toggleModal();
this.props.history.push('/quote');
// ..
};
render() {
//...
return(<Question {...props} next={this.onSubmit} />;)
}
}
export default withRouter(QuoteCalculator);
Now, everything works as expected. When the user submits, I go to the right route. I just see the following warning on the console
index.js:1446 Warning: withRouter(QuoteCalculator): Function
components do not support contextType.
I'm tempted to ignore the warning, but I don't think its a good idea.
I tried using Redirect alternatively. So something like
QuoteCalculator looks as follows:
class QuoteCalculator extends React.Component {
static contextType = ModalContext;
// ...
onSubmit = () => {
// ...
this.context.toggleModal();
this.setState({done: true});
// ..
};
render() {
let toDisplay;
if(this.state.done) {
toDisplay = <Redirect to="/quote"/>
} else {
toDipslay = <Question {...props} next={this.onSubmit} />;
}
return(<>{toDisplay}</>)
}
}
export default QuoteCalculator;
The problem with this approach is that I kept on getting the error
You tried to redirect to the same route you're currently on
Also, I'd rather not use this approach, just because then I'd have to undo the state done (otherwise when user clicks button again, done is true, and we'll just get redirected) ...
Any idea whats going on with withRouter and history.push?
Here's my app
class App extends Component {
render() {
return (
<Layout>
<Switch>
<Route path="/quote" component={Quote} />
<Route path="/pricing" component={Pricing} />
<Route path="/about" component={About} />
<Route path="/faq" component={FAQ} />
<Route path="/" exact component={Home} />
<Redirect to="/" />
</Switch>
</Layout>
);
}
}
Source of the warning
Unlike most higher order components, withRouter is wrapping the component you pass inside a functional component instead of a class component. But it's still calling hoistStatics, which is taking your contextType static and moving it to the function component returned by withRouter. That should usually be fine, but you've found an instance where it's not. You can check the repo code for more details, but it's short so I'm just going to drop the relevant lines here for you:
function withRouter(Component) {
// this is a functional component
const C = props => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<Route
children={routeComponentProps => (
<Component
{...remainingProps}
{...routeComponentProps}
ref={wrappedComponentRef}
/>
)}
/>
);
};
// ...
// hoistStatics moves statics from Component to C
return hoistStatics(C, Component);
}
It really shouldn't negatively impact anything. Your context will still work and will just be ignored on the wrapping component returned from withRouter. However, it's not difficult to alter things to remove that problem.
Possible Solutions
Simplest
Since all you need in your modal is history.push, you could just pass that as a prop from the modal's parent component. Given the setup you described, I'm guessing the modal is included in one place in the app, fairly high up in the component tree. If the component that includes your modal is already a Route component, then it has access to history and can just pass push along to the modal. If it's not, then wrap the parent component in withRouter to get access to the router props.
Not bad
You could also make your modal component a simple wrapper around your modal content/functionality, using the ModalContext.Consumer component to pass the needed context down as props instead of using contextType.
const Modal = () => (
<ModalContext.Consumer>
{value => <ModalContent {...value} />}
</ModalContext.Consumer>
)
class ModalContent extends React.Component {
onSubmit = () => {
// ...
this.props.toggleModal()
this.props.history.push('/quote')
// ..
}
// ...
}

react-router cannot read location of undefined

I'm trying to pass some values from one component to another using query strings.
This is the component I'm passing values from (shortened):
export class Button extends React.Component {
constructor(props) {
super(props);
this.state = {title : "some title"};
}
render() {
return(
<Button type="submit" color="primary">
<Link to="/Template_Main" query={{title: this.state.title}}>Save</Link>
</Button>
);
}
}
This is how I'm trying to get the value in my other component:
const title = this.props.location.query;
This is my router:
import React from 'react'
import {
BrowserRouter as Router,
Route,
browserHistory
} from 'react-router-dom';
import Home from './Main';
import TimelineTemplate from './Template_Main';
const App = () =>
<Router history={browserHistory}>
<div>
<Route exact path="/" component={Home}/>
<Route path="/Template_Main/:title" component={TimelineTemplate} />
</Route>
</div>
</Router>
export default App
So, for clarification: I shortened my code to show only what's important. In my Button-component (I chose the name for this post, it has a different name in my actual code), there is also a form in which you can enter a title. When clicking on the button, you are redirected to Template_Main. I want to display the value of title in Template_Main and wanted to pass the value using a query string.
However, I'm making a few mistakes somewhere.
For one, Template_Main is displayed as blank, when I add :title to path="/Template_Main/:title in the Router.
When entering a sub-route, like so:
<Route path="/Template_Main" component={TimelineTemplate}>
<Route path="/Template_Main/:title"/>
</ Route>
I am redirected, however, then I receive the error message that "location" of undefined cannot be read. I read that I need to pass history to <Router>, which I tried and which also failed since I received the error message that there was no property browserHistory in react-router-dom. Apparently there is no such thing in v4.0.0, so I tried to downgrade to 3.0.0 and then to 2.0.0 using npm install react-router#x.x.x, however, I still received the same error message.
I have been researching this for a few hours now and at this point I'm not really sure about what to do.
Did I make any mistakes, either in my code or in how I tried to solve the issue (I tried to describe my course of action as clearly as possible) and do you guy have any ideas about how to solve it?
location.query seems to have been discontinued in React router v4. You can try a location.search, props.location.pathname or props.match.params instead.
Here is a github issue for the same problem.
Here is a code example:
export class Button extends React.Component {
constructor(props) {
super(props);
this.state = {title : "some title"};
}
render() {
return(
<Button type="submit" color="primary">
<Link to={"/Template_Main/"+this.state.title}>Save</Link>
</Button>
);
}
}
and the child component:
import { withRouter } from 'react-router-dom';
class Child extends React.Component {
render(){
return <div>{this.props.match.params.title}</div>
}
}
export default withRouter(Child);
Router definition should be as follows:
<Route path="/Template_Main/:title" component={TimelineTemplate} />
Hope it helps.
PS: I am yet to figure out how to pass multiple parameters using this method. If I figure it out, I'll update this answer in the future.

Resources