React router 'to' through props in const - reactjs

I suspect this is more about React (or JS) than routing.
To avoid props collision, I am using forwardRef, defined in a const. The 'to' I want to set through props passed from parent component, and I cannot get this work. In the example below, where it says to={props.toUrl} it only works if it is to='/hello'. How do I do this, so I can use the ButtonRouter component from the Demo component?
import React from "react";
import { MemoryRouter as Router } from "react-router";
import { Link } from "react-router-dom";
import Button from "#material-ui/core/Button";
const CollisionLink = React.forwardRef((props, ref) => (
<Link innerRef={ref} to={props.toUrl} {...props} />
));
function ButtonRouter(props) {
return (
<Router>
<Button component={CollisionLink}>Routing w/o props collision</Button>
</Router>
);
}
export default function Demo() {
return <ButtonRouter toUrl="/hello" />;
}

I think you might be missing props from ButtonRouter:
function ButtonRouter(props) {
return (
<Router>
<Button component={(routerProps) => <CollisionLink {...routerProps} {...props} />}>Routing w/o props collision</Button>
</Router>
);
}

Related

Clicking on a Card component should create a new route and display further information

I'm new to react-router v6
I have 4 components, App, CardList, Card and CardInfo. There is data (an array of objects, each object represents a movie) coming from an API that gets saved in App.js with useState hook.
Within CardList, I use map to iterate over the array to generate a bunch of Card components and passing in data via props.
What I want now is to be able to click on any Card component and for it to navigate to a different route, e.g. localhost:3000/1 (for Card with the id of 1), localhost:3000/2 (for Card with the id of 2) etc. and within each route that corresponds to the Card id, there would be a box/modal (CardInfo.js) component with further information about the movie.
I'm trying to accomplish this with react-router-dom (version 6).
It looks like within the CardList.js or Card.js component you would need to create links (<Link>) and routes (<Route>) (both which are equal to the number of movies in the data) on the fly with the .map function and wrapping the Card component in <Link> and <Route> tags. Something like
{items.map(movie => (
<Route path="/:id" element={<Card items={movies} />} exact>
<Link to={`/${movie.id}`}>
<Card
key={movie.id}
id={movie.id}
name={movie.name}
description={movie.description}
img={movie.image_url}
/>
</Link>
</Route>
))}
Obviously that doesn't work.
App.js:
import './App.css';
import CardList from './features/Card/CardList';
import CardInfo from './features/Card/CardInfo';
import { useState, useEffect } from "react";
import { BrowserRouter, Route, Routes } from 'react-router-dom';
function App() {
const [movies, setMovies] = useState([]);
useEffect(() => {
const getData = fetch('https://api.com/movies')
.then(data => data.json())
.then(items => { setMovies(items) })
}, [])
return (
<BrowserRouter>
<Routes>
<Route path="/:id" element={<CardInfo items={movies} />} exact></Route>
</Routes>
<CardList items={movies} />
</BrowserRouter>
);
}
export default App;
CardList.js:
import React from 'react'
import Card from './Card'
import "./CardList.css";
const CardList = ({ items }) => {
return (
<div className="cardList">
{items.map(movie => (
<Card
key={movie.id}
id={movie.id}
name={movie.name}
description={movie.description}
img={movie.image_url}
/>
))}
</div>
)
}
export default CardList
Card.js:
import React from 'react'
import "./Card.css";
import { Link } from 'react-router-dom';
function Card(props) {
return (
<Link to={`/${props.id}`}>
<div className="card">
<div>
<img src={props.img} />
<h2>{props.name}</h2>
</div>
</div>
</Link>
)
}
export default Card
CardInfo.js:
import React from 'react'
function CardInfo(props) {
return (
<div>
<p>{props.description}</p>
</div>
)
}
export default CardInfo
your code structure is correct. You just need to useLink, you don't need en external Route component for every card you map.
Here is a link for further information. Hope you find it helpful.
https://stackoverflow.com/a/57059249/17715977

rendering component, after another distant component renders

In navigation menu app, down the component tree, there is a dropdown menu component DropdownMenu2, with menu items, which are <NavLinks> components. Every time an item is clicked, it points to one of the <Route>s in main App. Every <Route> is a page, containing Infofield component. So every time <NavLink> is clicked, Infofield is rendered.
My puzzle is: I need the HeaderLogo component be rendered, everytime Infofield is rendered (HeaderLogo contains animation). I failed when constructing useEffect hook in Infofield. That hook was intended to contain custom hook, producing a variable with changing state. That hook could be then lifted up to App, from there variable would be passed to HeaderLogo, inline to the key property. If that idea is legit, I'm experiencing difficulties with construction of custom hook inside of useEffect. Maybe (probably) there is a better way...
Apps most basic structure looks like this:
App
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
/...
import { UserContext } from "./components/sub-components/UserContext";
function App() {
return (
<Router>
<div className="App">
<HeaderLogo />
<NaviMain />
<Routes>
<Route path="/Info" element={<Info />} />
/...
</Routes>
</div>
</Router>
);
}
export default App;
NaviMain
import "./NaviMain.css";
import NaviMainButton from "./NaviMainButton";
import NaviMainButtonDrop2 from "./NaviMainButtonDrop";
const NaviMain = () => {
return (
<nav>
<ul>
<NaviMainButtonDrop2 />
</ul>
</nav>
)
}
export default NaviMain
NaviMainButtonDrop2
import DropdownMenu2 from "./DropdownMenu2";
const NaviMainButtonDrop2 = () => {
return (
<li>
<a>
title
</a>
<DropdownMenu2 />
</li>
)
}
export default NaviMainButtonDrop2
DropdownMenu2
import "./DropdownMenu.css"
import { NavLink } from "react-router-dom";
import { MenuItemContentSchool } from "./sub-components/MenuItemContentSchool"
const DropdownMenu2 = () => {
return (
<div className=dropdown-holder-us>
{/* here menu unfolds */}
{MenuItemContentSchool.map((item) => {
return (
<NavLink
to={item.link}
className={(navData) => (navData.isActive ? "d-content-us active-style" : 'd-content-us')}
key={item.id}
>
{item.title}
</NavLink>
)
})}
</div>
)
}
export default DropdownMenu2
Info (one of the <Route>'s )
import InfoField from "../components/InfoField"
const Info = () => {
return (
<section className="intro-index">
<InfoField text={"welcome"} />
</section>
)
}
export default Info
HeaderLogo
import "./HeaderLogo.css";
const HeaderLogo = () => {
return (
<header>
<h1 className="head-main">learning curve</h1>
</header>
)
}
export default HeaderLogo
From what I can gather you simply want to "rerun" an animation in the HeaderLogo component when the path changes. Import and use the useLocation hook and use the pathname value as a React key on the header element with the animation to want to run when it mounts. The idea here is that when the React key changes, React will remount that element.
Example:
import { useLocation } from "react-router-dom";
import "./HeaderLogo.css";
const HeaderLogo = () => {
const { pathname } = useLocation();
return (
<header>
<h1 key={pathname} className="head-main">
learning curve
</h1>
</header>
);
};
export default HeaderLogo;
This is a classic job for a global state. You can declare a boolean state, i.e showHeader, and add conditional rendering to the tag.
The global state variable showHeader will be changed each time you click on a dropdown item, and in the App functional component you should listen for a change in this variable. (For example, using Redux, you'll use useSelector(state=>state.showHeader) in App.
For an example, this is the App component with conditional rendering for the HeaderLogo. In order for this to be useable, you need to build a Redux store and reducer functions. Read the official Redux docs for more
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { useSelector } from 'react-redux';
import HeaderLogo from "./components/HeaderLogo";
import NaviMain from "./components/NaviMain";
import Info from "./pages/Info";
/...
import { UserContext } from "./components/sub-components/UserContext";
function App() {
const showHeader = useSelector(state=>state.showHeader)
return (
<Router>
<div className="App">
{showHeader ? <HeaderLogo /> : null}
<NaviMain />
<Routes>
<Route path="/Info" element={<Info />} />
/...
</Routes>
</div>
</Router>
);
}
export default App;
</Router>

How to make a lazy loading high order component in React

So here is the main idea, HOC that is be able to load any wrapped in component with React.lazy and React.Suspense. Is it possible???
So, I already was able to write some, but not sure that I was able to made properly...
import React, { Suspense, lazy, useState, useEffect } from "react"
export function withLazyImport(LazyComponent) {
return (props) => {
const [loadedComponent, setLoadedComponent] = useState(null)
useEffect(() => {
setLoadedComponent(lazy(() => import(<LazyComponent {...props} />)))
//eslint-disable-next-line
}, [])
return (
<Suspense fallback="Lazy component is loading ...">
{loadedComponent}
</Suspense>
)
}
}
I don't understand why do you use useEffect. The resulted component will not pass new props to the lazy component because props are passed on did mount.
I came up with another solution based on the example provided by the author of this question
import React, { Suspense } from 'react';
export const withLazyComponent = (LazyComponent) => {
return (props) => (
<Suspense fallback="Lazy component is loading ...">
<LazyComponent {...props} />
</Suspense>
)
}
And then you use it like:
const LazyComponent = withLazyComponent(React.lazy(() => import('path/to/component')));
Here's how to achieve it in TypeScript
import { Loader } from "../components/loader";
import { Suspense } from "react";
/**
* HOC to wrap a component in a Suspense component.
*/
export default function withSuspense<P>(Component: React.ComponentType & any) {
return function WithSuspense(props: P) {
return (
<Suspense fallback={<Loader />}>
<Component {...props} />
</Suspense>
);
};
}
you can try using already existing solutions like Loadable Components

React-router custom prop not passing to component. ternary operator not working correctly

In React i have my App.js page where i keep my states. I'm importing user1.js component to App.js, and in user1.js component i have a link button that takes me to path /user2.
When i click the button, React will set state property called testValue to true and in user2.js page ternary operator should choose the first value - test works because of that. But for some reason it does not work.
Any help?
APP.JS
import React, { Component } from 'react';
import './App.css';
import User1 from './components/user1';
class App extends Component {
constructor(props){
super(props);
this.state = {
testValue:false
};
}
change = () => {
this.setState({
testValue:true
},() => {
console.log(this.state.testValue)
});
}
render() {
return (
<div className="App">
<User1 change={this.change}/>
</div>
);
}
}
export default App;
USER1.JS
import React from 'react';
import { BrowserRouter, Route, Switch, Link } from 'react-router-dom';
import User2 from './user2.js';
const User1 = (props) => {
return(
<BrowserRouter>
<div>
<Link to ="/user2">
<button onClick={props.change}>Next page</button>
</Link>
<Switch>
<Route path="/user2" exact component={User2}/>
</Switch>
</div>
</BrowserRouter>
); // end of return
};
export default User1;
USER2.JS
import React from 'react';
const User2 = (props) => {
console.log(props)
return(
<div>
{props.testValue ?
<p>test works</p>
:
<p>test does not work</p>
}
</div>
);
};
export default User2;
This is what i expected - test works
This is what i got - test does not work
You want to pass a custom property through to a component rendered via a route. Recommended way to do that is to use the render method.
<Route path="/user2" exact render={(props) => <User2 {...props} testValue={true} />} />
I think a valid inquiry here would be what are you wanting to pass through as an extra prop? whats the use case here? You may be trying to pass data in a way you shouldn't (context would be nice :D).

Props don't seem to be passed along in a React app

I recently started working with React so forgive me if my question is very basic. Props in a component don't seem to be passed along.
Below is my code.
dogDetails component
import React from 'react';
const DogDetails = (props) => {
return (
<div>
<h4>{'Dog details of '+ props.breed}</h4>
</div>
)
};
export default DogDetails;
In Dog component I have a method that returns a DogDetails component as shown below.
import React , {Component} from 'react'
import Dog from './Dog/Dog';
import classes from './Dogs.css';
import Aux from '../../hoc/Auxillary/Auxillary';
import {Route} from 'react-router-dom';
import DogDetails from './Dog/DogDetails/DogDetails';
class Dogs extends Component {
state = {
loadedDogs: []
};
componentDidMount () {
this.setState({
loadedDogs:[]
})
}
dogDetailsHandler = (dog) =>{
console.log(dog.breed);
return <DogDetails breed={dog.breed}/>;
};
render() {
const loadDogs = this.state.loadedDogs.map(dog => {
return <Dog
url={dog.images[0].image1}
alt={dog.id}
breed={dog.breed}
temperament={dog.temperament}
id={dog.id}
key={dog.id}
clicked ={() => this.dogDetailsHandler(dog)}>
</Dog>
});
return (
<Aux>
{loadDogs}
</Aux>
)
}
}
export default Dogs;
I have omitted the content of the loadedDogs array to reduce the code size.
Below is the Dog component
import React from 'react';
import classes from './Dog.css';
import {Link, Route} from 'react-router-dom';
const dog = (props) => {
return(
<div className={classes.Dog}>
<div>
<img src={props.url} alt ={props.id}/>
</div>
<div>
<h4>{'Breed: ' + props.breed}</h4>
<h5>{'Temperament: ' + props.temperament}</h5>
<p>
<Link to = '#'>... Read more ...</Link>
</p>
<Link to={'/shop/'+ props.id}>
<button onClick={props.clicked}>Order</button>
</Link>
</div>
</div>
)
};
export default dog;
I'm routing the DogDetails in the MainContent component like this.
import React from 'react';
import classes from './MainContent.css';
import Dogs from './Dogs/Dogs';
import Contact from '../Contact/Contact';
import {Route} from 'react-router-dom';
import DogDetails from './Dogs/Dog/DogDetails/DogDetails';
const main = () =>{
return (
<div className={classes.MainContent}>
<Route path='/' exact component = {Dogs}/>
<Route path='/shop' exact component = {Dogs}/>
<Route path={'/shop/:id'} exact component={DogDetails}/>
<Route path='/contact' exact component ={Contact}/>
</div>
)
};
export default main;
Here is a sample code sandbox to demonstrate what I'm trying to work on. I want the DogDetails component to show up when the Order button is clicked.
Code Sandbox
The dogDetails component <h4> tag is returning undefined. Please help me find where I'm doing it wrong.
Capitalize both dogDetails and dogDetailsHandler
User-Defined Components Must Be Capitalized
from react docs
Codesandbox example
Since you are using routing, I'm not sure why you have a button handler inside of a routed <Link />. Clicking on this element will route you to /shop/:id, and the return method of dogDetailsHandler will do nothing.
I have emulated your code and added a <Route /> I'm not sure if this is what you are after, but when I click "Order", I'll get routed to /shop/:id with the DogDetails component being rendered as it should.
Add this routing component after your <Link /> component and see if this is the behavior you are after.
<Route path="/shop/:id" render={
() => <DogDetails {...props} />
} />

Resources