How to share props to a different route using react-router-dom? - javascript

I am trying to share my props (data, saveWorkButtonClicked, updateFBRDB) from <ProjectPage /> component route to <Indent /> component route.
But getting the following error:
Uncaught DOMException: Failed to execute 'pushState' on 'History': async (data, setSpinner, updateFBRDB) => {
setSpinner && setSpinner(true);
let rawRoomData = String.raw`${J...<omitted>...
} could not be cloned.
App.js
<Router>
<Switch>
<Route path="/ProjectPage/:projectId" exact component={ProjectPage} />
<Route path="/Indent/" render={(props) => <Indent {...props} />} />
</Switch>
</Router>
ProjectPage.js
history.push("/Indent/",
{
data: { ...project, rooms: project.rooms, ProjectId: project.ProjectId, ClientName: project.ClientName, Address: project.Address, AmountRecieved: project.AmountReceived, SiteEngineerId: project.SiteEngineersId },
saveWorkButtonClicked,
updateFBRDB,
}
)
// saveWorkButtonClicked & updateFBRDB are API calls which will be called in <Indent />
Indent.js
export default function Indent({ data, saveWorkButtonClicked, updateFBRDB }) {
console.log('data in indent', data)
}
NOTE: Please give solutions where this can be implemented without Context/ Redux/ Mobx. Also, I am using react-router-dom v5.2.0

I would suggest an workaround. Have a state which keeps track of when you want to move to next page, so that we can use Redirect component conditionally with your desired data as props.
App.js
<Router>
<Switch>
<Route path="/ProjectPage/:projectId" exact component={ProjectPage} />
</Switch>
</Router>
ProjectPage.js
const [isDone, setIsDone] = useState(false);
const handleClick = () => {
// Do all your works, when you want to `push` to next page, set the state.
setIsDone(true);
}
if(isDone) {
return (
<>
<Route path="/Indent"
render={ props =>
<Indent
{...props}
data={...}
saveWorkButtonClicked={saveWorkButtonClicked}
updateFBRDB={updateFBRDB}
/>
}
/>
<Redirect to="/Indent" />
</>
);
}
return (
<div>Your Normal Profile Page goes here</div>
)

If you want to "share" props, you need to do one of two things. Either have the receiving component be a child of the propsharing component - in which case you can pass them as props directly. Else, you would need to pass them as state via a common ancestor component, which you would need to update by sending a callback down to the component that will update the state.

You can pass state to location with this format
const location = {
pathname: '/Indent/',
state: {
data: { ...project, rooms: project.rooms, ProjectId: project.ProjectId, ClientName: project.ClientName, Address: project.Address, AmountRecieved: project.AmountReceived, SiteEngineerId: project.SiteEngineersId },
saveWorkButtonClicked,
updateFBRDB,
}
}
history.push(location)
And then using withRouter to receive location values
import { withRouter } from 'react-router'
function Indent({ location }) {
const { state } = location
const { data, saveWorkButtonClicked, updateFBRDB } = state || {}
return <></>
}
export default withRouter(Indent)

Related

get useLocation props from Route : Uncaught TypeError: Cannot read properties of null (reading 'isNew')

I'm new to reactjs, I want to access to my component Bdc using two ways first when I click on a button in another component and the other way when I access the component using its path directly in search bar.
First way is working using useLocation
<Button
variant="contained"
onClick={() => navigate("/nouveauBdc", {state: {id: '', isNew: true}})}>
NOUVEAU BON DE COMMANDE
</Button>
In my component I can read state sent from navigate("/nouveauBdc", {state: {id: '', isNew: true}})
but the second way when I put the path of my component in search bar http://localhost:3001/nouveauBdc I got this error
Uncaught TypeError: Cannot read properties of null (reading 'isNew')
Bdc component : here is the line from where I got the error :
React.useEffect(() => {
setBdcState(location.state as BdcProps);
}, [location])
React.useEffect(() => {
if(!bdcState.isNew){
//get data
}
}, [bdcState.isNew]) // the line error
my Routes
I put the Link to pass the state from here but is not working
<BrowserRouter>
<Routes>
<Route path="/nouveauBdc" element={<ProtectedRoutes/>}>
<Route path="/nouveauBdc" element={<Bdc />} />
</Route>
<Route path="*" element={<h1>404 - Page non trouvée</h1>} />
<Route path="/accessDenied" element={<h1>403 - Accès interdit</h1>} />
</Routes>
<Link to={'nouveauBdc'} state={{ state: {id: '', isNew: true} }} >Page 1</Link>
</BrowserRouter>,
ProtectedRoutes.tsx
const user = () => {
if(userAuth)){
return(
<Outlet/>
);
} else {
return(
<Navigate to="/accessDenied"/>
);
}
}
return(
<div>
{user()}
</div>
);
Please is there any solution to send state as well when access component directly (search bar)
You cannot set the state directly from the location bar, it will always be null (and null, of course, doesn't have any properties, so you get the error).
If you want to display the component when the location is inserted in the URL bar, you should use location.search instead. That will add a query to your url, such as ?bcdState=new which you can read when the component loads similar to location.state.

React passing down hooks causes rerender and loss of focus on input

I've got a parent component in which I initialize some piece of state, which I then pass down to the children components so that they can update that. However, when the update is triggered, the component tree is re-rendered and my inputs lose focus. Adding a key did not help.
// App.tsx
export function App(props) {
const useVal = useState("");
return (
<Router>
<Switch>
<Route
exact
path="/"
component={() => (
<StartScreen
useVal={useVal}
/>
)}
/>
// ...
</Router>
);
}
// StartScreen.tsx
interface StartScreenProps {
useVal: [string, React.Dispatch<React.SetStateAction<string>>];
}
function bindState<T>(
[value, setState]: [T, React.Dispatch<React.SetStateAction<T>>]
) {
return {
value,
onChange: ({ value }: { value: T }) => setState(value)
}
}
export const StartScreen = (props: StartScreenProps) => {
return (
<form>
<InputField
key="myInput"
{...bindState(props.useVal)}
/>
</form>
);
}
So, now when I start typing on my InputField (which is basically a wrapper on an <input>) on StartScreen.tsx, the input constantly loses focus as the component is totally re-rendered (I can see it in the DOM).
This happens because you are passing a function to the Route's component prop (I assume you are using react-router-dom) :
From the docs :
If you provide an inline function to the component prop, you would
create a new component every render. This results in the existing
component unmounting and the new component mounting instead of just
updating the existing component.
To solve this problem use the render prop :
<Route
exact
path="/"
render={() => (
<StartScreen
useVal={useVal}
/>
)}
/>
This allows for convenient inline rendering and wrapping without the
undesired remounting explained above.

Setting up private routes with react router

I am trying to setup a website with a login screen for unauthorized users and a dashboard for authorized users using react router dom.
Every time there is a route change (dashboard routes) when a user clicks a link in the sidebar, for example. The useEffect inside dashboard component is called which fetches data that I already have.
## ROUTES ##
export const appRoutes = auth => [
{
path: '/',
component: () => auth ? <Redirect to='/dashboard' /> :<Login/>,
exact: true
},
{
path: '/dashboard',
component: Guilds ## REDIRECTS TO THE NEXT ROUTE WITH ID ##,
exact: true,
private: true
},
{
path: '/dashboard/:id',
component: Dashboard,
private: true
},
{
path: '/dashboard/*',
component: Dashboard,
private: true
}
]
export const dashboardRoutes = [
{
path: '/dashboard/:id',
component: Home,
exact: true
}
]
## SIMPLIFIED APP COMPONENT ##
export default function App() {
return (
<ThemeProvider theme={theme}>
<BrowserRouter>
<Switch>
{appRoutes(auth).map(value => {
if(value.private) return <PrivateRoute path={value.path} component={value.component} exact={value.exact} key={value.path} auth={auth} />;
else return <Route path={value.path} component={value.component} exact={value.exact} key={value.path} />;
})}
</Switch>
</BrowserRouter>
</ThemeProvider>
)
}
## SIMPLIFIED DASHBOARD COMPONENT ##
export default function Dashboard({ match }) {
const [guild, setGuild] = useState(null);
const [user, setUser] = useState(null);
useEffect(() => {
getGuild(match.params.id)
.then(res => {
setGuild(res.data);
return getUser();
})
.then(res => {
setUser(res.data);
})
.catch(err => {
console.log(err);
})
}, [match.params.id]);
return (
<div className={classes.root}>
<Header onToggleDrawer={onToggleDrawer} guild={guild} auth />
<SideBar onToggleDrawer={onToggleDrawer} isOpen={drawerOpen} user={user} />
<div className={classes.content}>
<div className={classes.toolbar} />
<div className={classes.contentContainer}>
{dashboardRoutes.map(value => {
return <Route exact={value.exact} path={value.path} component={value.component} key={value.path}/>
})}
</div>
</div>
</div>
)
}
## PRIVATE ROUTE COMPONENT ##
export const PrivateRoute = ({ component: Component, auth, ...rest }) => {
return (
<Route {...rest} render={(props) => (
auth
? <Component {...props} />
: <Redirect to='/' />
)} />
)
}
I'm not sure if I am approaching the situation correctly but any help would be great. I take it the function is called in-case a user comes to the site from a bookmark for example but if someone can shed some light that would be cool.
Thank you.
The reason behind that why the fetch is happening several times is the dependency array what you have for useEffect. I assume the match.params.id is changing when the user clicks then it changes the route which will trigger the fetch again.
Possible solutions:
1. Empty dependency array:
One possible solution can be if you would like to fetch only once your data is set the dependency array empty for useEffect. From the documentation:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.
So if you have the following, it will run only once:
useEffect(() => {
// this part runs only once
}, []); // empty dependency array
2. Checking if the fetch happened already:
The other solution what I was thinking is to check if you have the value already in the guild variable just like below:
useEffect(() => {
// no value presented for guild
if (guild === null) {
// code which is running the fetch part
}
}, [match.params.id]);
I hope this gives you an idea and helps!

how to Stop rerendering of entire component onChange event on input text field in reactJs

I m new to reactJs and i m creating user Authentication functionality. I have two components one is header which has navbar and it contains react-router routers and the other is login component which has two input fields ... The problem with login component is when i start typing in input field it loses focus after each character typed i know it is rerendering the whole component but i don't know how to solve this problem
header.js
changeName = (e) => {
this.setState({name : e.target.value})
}
changePass = (e) => {
this.setState({password:e.target.value})
}
login = () => {
var name = this.state.name;
var password = this.state.password
var mysession;
$.ajax({
url : 'http://localhost:4000/login',
type : "POST",
data : {username:name,password:password},
success : function(data){
if(data == true){
this.setState({sessionFlag:true})
$('#home')[0].click();
}
else {
this.setState({sessionFlag:false})
}
}.bind(this)
})
}
render(){
const {name,password} = this.state;
return (
<Router>
<div>
<Route path="/login" exact component={()=><Login
onClickHandle={this.login.bind(this)}
onChangeName={this.changeName.bind(this)}
onChangePass={this.changePass.bind(this)}
name={name}
password = {password} />} />
</div>
</Router>
)
}
login.js
render(){
return (
<form className="form-horizontal" method ="post">
<input
type="text"
onChange={this.props.onChangeName}
value={this.props.name}/>
<input type="text"
onChange={this.props.onChangePass}
value={this.props.password} />
<input type="button"
value="Login"
onClick={this.props.onClickHandle} />
</form>
)
}
The main issue is the manner in which you are specifying your Login component:
<Route
path="/login"
exact
component={() => (
<Login
onChangeName={this.changeName.bind(this)}
onChangePass={this.changePass.bind(this)}
name={this.state.name}
password={this.state.password}
/>
)}
/>
Using this syntax causes the child of the Route to look like a brand-new type of component with each rendering (since it will be a new arrow function instance each time) so the previous Login component will be completely unmounted and the new one mounted.
From https://reactrouter.com/web/api/Route/component:
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop (below).
Here is an example using the render-func approach:
Header.js
import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import Login from "./Login";
class Header extends React.Component {
constructor(props) {
super(props);
this.state = { name: "", password: "" };
this.changeName = this.changeName.bind(this);
this.changePass = this.changePass.bind(this);
}
changeName = (e) => {
this.setState({ name: e.target.value });
};
changePass = (e) => {
this.setState({ password: e.target.value });
};
render() {
return (
<Router>
<div>
<div>
<Link to="/login">Login</Link>
</div>
<Route
path="/login"
exact
render={() => (
<Login
onChangeName={this.changeName}
onChangePass={this.changePass}
name={this.state.name}
password={this.state.password}
/>
)}
/>
</div>
</Router>
);
}
}
export default Header;

Component only rending if I start the flow from the homepage

I am having an issue with my application. My user component only loads UserCard when I start the application from the homepage then click users link there... if I just refresh the users URL... UserCard doesn't get loaded which means something is wrong with my this.props.users. I do see that in chrome it says: Value below was evaluated just now when I refresh but when I go through the flow it doesn't say that. Any help will be appreciated.
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
users: []
};
}
componentDidMount() {
users = []
axios.get('/getall').then((res) => {
for(var d in res.data) {
users.push(new User(res.data[d]));
}
});
this.setState({ users });
}
render() {
const { users } = this.state;
return (
<Router history={history}>
<Switch>
<PrivateRoute exact path="/" component={Home} />
<Route exact path='/users' render={(props) => <Users {...props} users={users} />}/>
</Switch>
</Router>
)
}
}
PrivateRoute:
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={props => (
<Component {...props} /> )} />
)
User.js
export default class Users extends Component {
render() {
console.log(this.props.users);
return (
<Row>
{this.props.users.map(u =>
<UserCard key={u.name} user={u}/>
)}
</Row>
);
}
}
export class User {
constructor(obj) {
for (var prop in obj){
this[prop] = obj[prop];
}
}
getURLName() {
return this.name.replace(/\s+/g, '-').toLowerCase();
}
}
class UserCard extends Component {
render() {
return (
<Link to={'/users/' + this.props.user.getURLName()} >
<div>
// Stuff Here
</div>
</Link>
);
}
}
As per the comments:
The issue here is how you're setting state. You should never modify state directly since this will not cause the component to rerender See the react docs
Some additional thoughts unrelated to the question:
As per the comments - use function components whenever possible, especially with hooks on the way
There is probably no need to create a User class, only to new up little user objects. Simply use plain old JS objects and calculate the link url right in the place its used:
render() {
const { user } = this.props
return <Link to={`/users/${user.name.replace(/\s+/g, '-').toLowerCase()}`} />
}
It might be a good idea to start using a linter such as eslint. I see that you're declaring users = [] without using let or const (don't use var). This is bad practice since creating variables in this way pollutes the global name space. Linters like eslint will help you catch issues like this while you're coding.

Categories