How to send data in Link component to other component - javascript

I want to build a basic app. I have a Questions page and I keep the answers of the questions in answers array. After the test I want to send user to Results page with Link for see the score. So I need the use answers array in the Results component but when I try this I just get an empty array. It's obvious there is a mistake in my Routing or something. I cropped to parts that I thougt unnecessary. If you need more info about that just let me know.
Here is my App.js
function App() {
const [answers,setAnswers]=React.useState();
return (
<div className="App">
<Router>
<Routes>
<Route exact path="/" element={<Homepage />} />
<Route exact path="/questions" element={<Questions />} />
<Route exact path='/results' element={<Results state={answers}/>} />
</Routes>
</Router>
</div>
);
}
export default App;
Questions.js
function Questions() {
const [answers, setAnswers] = React.useState([]);
return(
<div>
<h1>Questions</h1>
<Link to={{pathname:"/results"}} state={answers}>
Go to Results
</Link>
</div>
)
}
Results.js
function Results(props){
const [answers, setAnswers] = useState([props.state]);
console.log(answers)
return(
<div>
Result Page
</div>
)
}

First of all, don't add data from props to state, it will only make it harder
https://codesandbox.io/s/modern-glitter-546kuv?file=/src/App.js
Pass state to the components in App
export default function App() {
const [answers, setAnswers] = React.useState([]);
return (
<div className="App">
<BrowserRouter>
<Routes>
<Route
exact
path="/"
element={<Questions setAnswers={setAnswers} />}
/>
<Route
exact
path="/results"
element={<Results answers={answers} />}
/>
</Routes>
</BrowserRouter>
</div>
);
}
Show answers in Results
function Results({ answers }) {
return (
<div>
Result Page
{answers.map((q) => (
<p>{q}</p>
))}
</div>
);
}

Related

React shows another page and then the Homepage, whe navigating to Homepage

I am new to React and I am using the Traversy crash course and the extra video about the react router 6+.
My Routes are like
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
return (
<div className="container">
<Router>
<Header
title='sikyona'
onAdd={()=> setShowAddtask(!showAddTask)}
showAdd={showAddTask}
/>
<Routes>
<Route
path='/'
element={
<>
{showAddTask && <AddTask onAdd={addTask} />}
{tasks.length > 0
? <Tasks tasks={tasks} onDelete={deleteTask} />
:<p>add tasks</p>
}
</>
}
/>
<Route path='/about' element={<About/>} />
</Routes>
<Footer />
</Router>
</div>
);
The problem is that when I navigate to the homepage http://localhost:3000/ I first see the About page for a second, and then the homepage (Route path='/'...)
I have "react-router-dom": "^6.4.1",
What is this happening and how can I fix it?
The issue isn't that the "About" page or About component is being rendered when the app is loading or navigating to "/" for the first time. It's that the app is actually on "/" and there's no tasks to display just yet and the UI is rendering the container, Header, the Route for path="/" with the "add tasks" text, and the Footer which renders a link to "/about".
Contrast this rendered UI with the actual "/about" path and About component.
Perhaps the UI/UX is fine for you with regards to this behavior and understanding what exactly is being rendered when, and for what reason. If on the other hand you don't want to see any of the UI until data has been loaded you can tweak the code to render nothing or some loading indicator while the tasks are fetched.
Example:
function App() {
const [showAddTask, setShowAddtask] = useState(false);
const [tasks, setTasks] = useState(); // <-- initially undefined
useEffect(() => {
const getTasks = async () => {
try {
const tasks = await fetchTasks();
setTasks(tasks); // <-- defined and populated
} catch(error) {
// log errors, display message, etc... or ignore
setTasks([]); // <-- defined and empty
}
};
getTasks();
}, []);
...
if (!tasks) {
return null; // <-- return null or loading indicator/spinner/etc
}
return (
<div className="container">
<Router>
<Header
title="hello"
onAdd={() => setShowAddtask(show => !show)}
showAdd={showAddTask}
/>
<Routes>
<Route
path="/"
element={
<>
{showAddTask && <AddTask onAdd={addTask} />}
{tasks.length ? (
<Tasks tasks={tasks} onDelete={deleteTask} />
) : (
<p>add tasks</p>
)}
</>
}
/>
<Route path="/about" element={<About />} />
<Route path="/task-details/:id" element={<TaskDetails />} />
</Routes>
<Footer />
</Router>
</div>
);
}
Try to put your <Route path='/' /> last in the list.

Rendering two different components with Reactjs

I've 2 different components, and I would like to render them separately. Logic is simple when user is logged in render 1st component, if it's not render default component.
Please review my code, and help me with this, I'm not good at react, so please help me
export default function App(props) {
const Authenticated = props.Authenticated
const RoutingCabinet = (
<React.Fragment>
<Header/>
<Switch>
<Route exact path = '/' component = { () => TempBody} />
<Route exact path = '/new' component = { () => <ResponsiveDrawer />}/>
</Switch>
<Footer/>
</React.Fragment>
);
const RoutingContent = (
<React.Fragment>
<Cabinet />
<Switch>
<Route exact path = '/user.test'component = { () => <div>Element</div> }/>
</Switch>
</React.Fragment>
);
return(
<ThemeProvider theme = {Theme}>
<BrowserRouter>
{
Authenticated
? RoutingCabinet
: RoutingContent
}
</BrowserRouter>
</ThemeProvider>
);
}
Currently I can see only RoutingCabinet
Here is the index.js
ReactDOM.render(<App Authenticated = {false} />, document.getElementById('__body__', '__root__'));
I may be wrong here, but is <Cabinet /> the same as <RoutingCabinet />? If so, it might work ok. The <div>Element</div> will be visible only if url matches user.test. If you are on any other page, this won't be rendered, so it'll look like <RoutingCabinet /> is rendered, while in fact it's <RoutingContent />, it just renders the same output.

How to detect matched route from a component outside of the <Route/> component that was matched using react-router?

I've got the following structure in my React app, using react-router-dom.
<Router>
<Header/>
<Main>
<AllRoutes> // THIS HANDLES THE SWITCH WITH ALL THE ROUTES
<Switch>
<Route exact path={ROUTES.HOME} component={Home}/>
<Route exact path={ROUTES.ABOUT} component={About}/>
<Route exact path={ROUTES.PRIVACY} component={Privacy}/>
// ETC
</Switch>
</AllRoutes>
</Main>
<Footer/> // <==== FOOTER NEEDS TO KNOW WHICH ROUTE HAS BEEN MATCH
<Router>
QUESTION
Footer needs to know what <Route/> has been match. What is the best pattern to achieve that?
OPTION #1
I found the useRouteMatch hook over on react router docs:
This would kind of work, but I don't think it is good enough for my situation. Because a URL string can match a route and still don't be a valid route at the same time.
For example:
Route: /:language/privacy
Valid route: /en/privacy
Not valid route that would also match: /notALanguage/privacy
Once a route has match, I usually need to check if it is valid before rendering a component page or the 404 page.
Like:
<Route exact path={"/:language/privacy"} render={(routeProps) => {
const possibleLanguage = routeProps.match.params.language;
if (possibleLanguage in LANGUAGES) {
return(
<PrivacyPage lang={possibleLanguage}/>
);
}
else {
return(
<Page404/>
);
}
}}/>
OPTION #2
What I'm thinking about doing is:
App.js calls useLocation. So it always re-render when there is a route change.
I could add a detectRoute function in App.js to do all the route checking beforehand.
And my AllRoutes component wouldn't need a component. I would implement a native JS switch and render the corresponding route.
This way I know upfront which <Route/> is going to match and I can pass it on to <Footer/> or any component that lives outside of the matched <Route/>.
Something like this:
SandBox Link
export default function App() {
console.log("Rendering App...");
const location = useLocation();
// THIS WOULD BE THE detectRoute FUNCTION
// I COULD EVEN USE THE useRouteMatch HOOK IN HERE
const matchedRoute =
location.pathname === ROUTE1
? "ROUTE1"
: location.pathname === ROUTE2
? "ROUTE2"
: "404";
return (
<div>
<div className="App">
<Link to={ROUTE1}>Route 1</Link>
<Link to={ROUTE2}>Route 2</Link>
<Link to={"/whatever"}>Route 404</Link>
</div>
<div>
<AllRoutes matchedRoute={matchedRoute} />
</div>
</div>
);
}
function AllRoutes(props) {
switch (props.matchedRoute) {
case "ROUTE1":
return <Route exact path={ROUTE1} component={Page1} />;
case "ROUTE2":
return <Route exact path={ROUTE2} component={Page2} />;
default:
return <Route exact path={"*"} component={Page404} />;
}
}
It works. But I would like to know if there's a proper way of doing this, 'cause this seems a bit weird and there might be something out there that was specifically designed for this.
Generally you want to either:
Wrap the components together
Create another switch to route them (and pass match params)
I put together a somewhat comprehensive example of the options. Hope that helps!
import React from "react";
import "./styles.css";
import { Switch, Link, Route, BrowserRouter } from "react-router-dom";
const hoc = (Component, value) => () => (
<>
<main>
<Component />
</main>
<Footer value={value} />
</>
);
const Wrapper = ({ component: Component, value }) => (
<>
<main>
<Component />
</main>
<Footer value={value} />
</>
);
const WrapperRoute = ({ component, value, ...other }) => (
<Route
{...other}
render={props => <Wrapper component={component} value={value} {...props} />}
/>
);
const Footer = ({ value }) => <footer>Footer! {value}</footer>;
const Header = () => <header>Header!</header>;
const Another = () => <Link to="/onemore">One More!</Link>;
const Home = () => <Link to="/other">Other!</Link>;
const OneMore = () => <Link to="/">Home!</Link>;
const Other = () => <Link to="/another">Another!</Link>;
export default () => (
<BrowserRouter>
<Header />
<Switch>
{/* You could inline it! */}
<Route
path="/another"
render={() => (
<>
<main>
<Another />
</main>
<Footer value="" />
</>
)}
/>
{/* You could use a custom route component (that uses an HOC or a wrapper) */}
<WrapperRoute
component={OneMore}
path="/onemore"
value="I got one more!"
/>
{/* You could use a Higher-Order Component! */}
<Route path="/other" component={hoc(Other, "I got other!")} />
{/* You could use a wrapper component! */}
<Route
path="/"
render={() => <Wrapper component={Home} value="I got home!" />}
/>
</Switch>
{/* You could have another switch for your footer (inline or within the component) */}
<Switch>
<Route
path="/another"
render={() => <Footer value="Switch footer another!" />}
/>
<Route
path="/other"
render={() => <Footer value="Switch footer other!" />}
/>
<Route
path="/onemore"
render={() => <Footer value="Switch footer onemore!" />}
/>
<Route path="/" render={() => <Footer value="Switch footer home!" />} />
</Switch>
</BrowserRouter>
);
Note the WrapperRoute would allow you to do validation on your match params before passing them through. You could do a Redirect if needed.
What I've ended up doing:
Since I'm using Redux, I added a piece of global state to keep track of the matched route.
And I dispatch actions to update that state from the render prop from the <Route/>'s component.
<Switch>
<Route key={index} exact path={"/some-route"} render={(routeProps) => {
// HERE I DISPATCH AN ACTION TO CHANGE THE STATE FOR THE CURRENT ROUTE
dispatch({
type: UPDATE_CURRENT_ROUTE,
payload: { name: "SOME_ROUTE_NAME" }
});
return (
<PrivacyPage
{...routeProps}
/>
);
}}/>
</Switch>
And now I can do on Footer.js:
function Footer() {
const currentRoute = useSelector((state) => state.currentRoute);
// RENDER FOOTER ACCORDINGLY TO THE CURRENT ROUTE
}

React Router path var with other paths as well, React cant tell the difference

I am setting up react router for different link in my project but the problem is I need react router to tell the difference between a user username variable and other paths.
For example:
baseUrl/admin
baseUrl/daniel
React doesnt know the difference. I will have a list of usernames in a db and would return an error if the user doesnt exist then that means the page does not exist.
This is my code:
class App extends Component{
render(){
return (
<Router>
<Route exact path="/" render={props => (
<React.Fragment>
<h1>Hey</h1>
</React.Fragment>
)}
/>
<Route exact path="/admin" render={props => (
<React.Fragment>
<h1>admin</h1>
</React.Fragment>
)}
/>
<Route path="/:user" component={UserComponent}
/>
</Router>
);
}
}
You can use the match.url property to choose which component render, for example:
<Route path="/:user" render={props => {
if(props.match.url === '/admin') {
return (
<React.Fragment>
<h1>Hey</h1>
</React.Fragment>
)
} else return (<UserComponent {...props} />)
}} />

React Router 4 passing props through Route render doesn't work

This question has been asked, but other answers aren't solving this for me. I want to pass the signInWithEmail handler function from my <App /> component down through <Layout /> and then to the <SignIn /> component via a <Route />.
The way to do this is apparently via the render attribute on the Route, but for me it just renders ignores my onSubmit. In React dev tools I just see the default Route props, even though I can see my handler function(s) in the <Layout /> element showing up as props.signInWithEmail. They don't make it to the <SignIn /> element.
What am I missing? Thanks.
const Layout = (props) => (
// i can console.log(props) here and see props.signInWithEmail
<div className="layout">
// header etc...
<main>
<Switch>
// ... other routes
<Route
path="/signin"
render= {(props) => <SignIn onSubmit={props.signInWithEmail} />}
/>
</Switch>
</main>
// footer etc...
</div>
)}
render part of App:
render() {
return (
<div className="App">
<BrowserRouter>
<Layout
signInWithEmail={this.signInWithEmail}
signUpWithEmail={this.signUpWithEmail}
signOut={this.signOut}
state={this.state}
/>
</BrowserRouter>
</div>
);
}
It happens because you overriding props within arrow function.
Try to destructure Layout prop like that:
const Layout = ({signInWithEmail}) => (
// i can console.log(props) here and see props.signInWithEmail
<div className="layout">
// header etc...
<main>
<Switch>
// ... other routes
<Route
path="/signin"
render= {() => <SignIn onSubmit={signInWithEmail} />}
/>
</Switch>
</main>
// footer etc...
</div>
)}

Categories

Resources