react-router-dom v6 useNavigate passing value to another component - reactjs

The version of react-router-dom is v6 and I'm having trouble with passing values to another component using Navigate.
I want to pass selected rows to another page called Report. But, I'm not sure I'm using the right syntax for navigate method and I don't know how to get that state in the Report component.
Material-ui Table: I'm trying to use redirectToReport(rowData) in onClick parameter.
function TableRows(props){
return (
<MaterialTable
title="Leads"
columns={[
...
]}
data = {props.leads}
options={{
selection: true,
filtering: true,
sorting: true
}}
actions = {[{
position: "toolbarOnSelect",
tooltip: 'Generate a report based on selected leads.',
icon: 'addchart',
onClick: (event, rowData) => {
console.log("Row Data: " , rowData)
props.redirect(rowData)
}
}]}
/>
)}
LeadTable component
export default function LeadTable(props) {
let navigate = useNavigate();
const [leads, setLeads] = useState([]);
const [loading, setLoading] = useState(true);
async function fetchUrl(url) {
const response = await fetch(url);
const json = await response.json();
setLeads(json[0]);
setLoading(false);
}
useEffect(() => {
fetchUrl("http://localhost:5000/api/leads");
}, []);
function redirectToReport(rowData) {
navigate('/app/report', { state: rowData }); // ??? I'm not sure if this is the right way
}
return(
<div>
<TableRows leads={leads} redirect={redirectToReport}></TableRows>
</div>
)}
Report component
export default function ReportPage(state) {
return (
<div>
{ console.log(state) // This doesn't show anything. How to use the state that were passed from Table component here?}
<div className = "Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);}

version 6 react-router-dom
I know the question got answered but I feel this might be helpful example for those who want to use functional components and they are in search of passing data between components using react-router-dom v6.
Let's suppose we have two functional components, first component A, second component B. The component A wants to share data to component B.
usage of hooks: (useLocation,useNavigate)
import {Link, useNavigate} from 'react-router-dom';
function ComponentA(props) {
const navigate = useNavigate();
const toComponentB=()=>{
navigate('/componentB',{state:{id:1,name:'sabaoon'}});
}
return (
<>
<div> <a onClick={()=>{toComponentB()}}>Component B<a/></div>
</>
);
}
export default ComponentA;
Now we will get the data in Component B.
import {useLocation} from 'react-router-dom';
function ComponentB() {
const location = useLocation();
return (
<>
<div>{location.state.name}</div>
</>
)
}
export default ComponentB;
Note: you can use HOC if you are using class components as hooks won't work in class components.

Your navigate('/app/report', { state: rowData }); looks correct to me.
react-router-v6
If you need state, use navigate('success', { state }).
navigate
interface NavigateFunction {
(
to: To,
options?: { replace?: boolean; state?: any }
): void;
(delta: number): void;
}
Your ReportPage needs to be rendered under the same Router that the component doing the push is under.
Route props are no longer passed to rendered components, as they are now passed as JSX literals. To access route state it must be done so via the useLocation hook.
function ReportPage(props) {
const { state } = useLocation();
console.log(state);
return (
<div>
<div className="Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);
}
If the component isn't able to use React hooks then you still access the route state via a custom withRouter Higher Order Component. Here's an example simple withRouter HOC to pass the location as a prop.
import { useLocation, /* other hooks */ } from 'react-router-dom';
const withRouter = WrappedComponent => props => {
const location = useLocation();
// other hooks
return (
<WrappedComponent
{...props}
{...{ location, /* other hooks */ }}
/>
);
};
Then access via props as was done in pre-RRDv6.
class ReportPage extends Component {
...
render() {
console.log(this.props.location.state);
return (
<div>
<div className="Top3">
<h3>Top 3 Leads</h3>
<ReportTop3 leads={[]} />
</div>
</div>
);
}
}

2 things (just a suggestion):
Rather than a ternary use &&
{location && <div>{location.state.name}</div>}
Why are you checking location and rendering location.state.name? I would use the check on the data you are fetching or make sure the data returns null or your value.

On Sabaoon Bedar's Answer, you can check if there is any data or not before showing it :
Instead of this <div>{location.state.name}</div>
Do this { location != null ? <div>{location.state.name}</div> : ""}

if you want to send data with usenavigate in functional component you can use like that
navigate(`/take-quiz/${id}`, { state: { quiz } });
and you can get it with uselocation hook like this
const location = useLocation();
location.state.quiz there is your data
But you cannot get this data in props it;s tricky part ;)!!

on SABAOON BEDAR answer,
from component A: navigate('/', {state:"whatever"}
in component B: console.log(location.state) //output = whatever

Related

how to handle props between components in react

i have came accross a problem where i am passing three props to a component Landingheader from parent Landing.js now i have another component called Cart and i want to use LandingHeader
as child component inside Cart but then i would also have to pass all the three props again to Landingheader which is very difficult and alot of code to rewrite
here is the code in Landing.js
<div>
<Landingheader
fetchproductResults={fetchproductResults}
user={user}
cartValue={cartValue}
/>
above you can see landingHeader component is getting three differenct props
here is my cart component where i want to resuse landingHeader component
import { Fragment } from "react";
import Landingheader from "./landingHeader";
const Cart = () => {
return (
<Fragment>
<Landingheader />
</Fragment>
);
}
export default Cart;
so above the landingHeader will now require three props so this means i would have to rewrite the whole logic again? how to solve this propblem? thanks
code for fetchproductResults
const fetchproductResults = (keyword) => {
setWord(keyword);
if (keyword !== "") {
const searchedRs = allproducts.filter((eachproduct) => {
return Object.values(eachproduct)
.join("")
.toLowerCase("")
.includes(keyword.toLowerCase());
});
setResult(searchedRs);
} else {
setResult(allproducts);
}
};
In case you don't need to pass any props to Landingheader from Cart you could use default value props in Landingheader. Something like:
const Landingheader = (props) => {
const { fetchproductResults = [], user = "", cartValue = "" } = props;
return (...);
}
export default Landingheader;
You can use context instead of props
in Landing component:
const MyContext=createContext(null)
const Landing=()=>{
.......
return (<MyContext.Provider value={[fetchproductResults,user,cartValue]}>
... all child compoenents
</MyContext.Provider/>
Now in Landingheader :
const [fetchproductResults,user,cartValue]=useContext(MyContext) /// use them as you like
Now you don't need to pass any props to either Cart or LandingHeader, it is receiving the data through context.

Is there a way to render a dynamically created component next to its sibling

I want to render dynamically created component next to its sibling.
I tried using
const onEnter = (e) => {
...
ReactDOM.render(<Breakdown message={message} />, e.currentTarget);
}
But ReactDOM.render clears its children components and replaces it with my new component.
Is there like a jquery $(e.currentTarget).append() function in React?
Try this:
const onEnter = (e) => {
...
const elem = document.createElement("DIV");
e.currentTarget.appendChild(elem);
ReactDOM.render(<Breakdown message={message} />, elem);
}
However, it is up to you refining the actual code.
Usually you would want to use state to manage showing/hiding a component. First import the useState hook like so
import React, { useState } from 'react'
Then you can use a boolean state to keep track of whether or not you should show Breakdown
const [showBreakdown, setShowBreakdown] = useState(false)
const onEnter = (e) => {
setShowBreakdown(false)
}
...
return (
<React.Fragment>
...
<SomeComponent>
<Breakdown message={message} />
</React.Fragment>
);

React HoC - props are not passed to wrapped component

I have two HoC component. First have to serve as some Layout wrapper which will contain some logic for mobile rendering etc.
const LayoutWrapper = (Component: React.FC<any>): React.FC<any> => {
const Layout = () => {
const [layout, set] = React.useState("layout state");
return <Component
layout={layout}
/>;
}
return Layout;
} export default LayoutWrapper;
Second HoC will take care of if user is logged in.
const Secured = (Component: React.FC<any>): React.FC<any> => {
const Wrapped = () => {
const [securedPagestate, set] = React.useState("secured page state");
const Layout = LayoutWrapper(Component);
return <Layout test={securedPagestate} />
}
return Wrapped;
}
export default Secured;
I have wrapped homepage component which will render actual page, and it needs to have props passed from both HoC components which are shown above, but I only get props passed from LayoutWrapper Hoc and not from Secured Hoc component. What is actually wrong with it?
const HomepageView = (props: HomepageViewProps) => {
return <>HOMEPAGE</>;
}
export default Secured(HomepageView);
If you want to pass props to your wrapped components, you have to do it this way:
const Layout = (props) => {
const Wrapped = (props) => {
In the React world, HOC are functions, not components, therefore they should start with a lower case letter: layoutWrapper and secured
// HIGHER ORDER COMPOENTS IN REACT
// Higher order components are JavaScript functions used for adding
// additional functionalities to the existing component.
// file 1: hoc.js (will write our higher order component logic) -- code start -->
const messageCheckHOC = (OriginalComponent) => {
// OriginalComponent is component passed to HOC
const NewComponent = (props) => {
// business logic of HOC
if (!props.isAllowedToView) {
return <b> Not Allowed To View The MSG </b>;
}
// here we can pass the props to component
return <OriginalComponent {...props} />;
};
// returning new Component with updated Props and UI
return NewComponent;
};
export default messageCheckHOC;
// file 1: hoc.js -- code end -->
// file 2: message.js -- code start -->
// this is the basic component we are wrapping with HOC
// to check the permission isAllowedToView msg if not display fallback UI
import messageCheckHOC from "./hoc";
const MSG = ({ name, msg }) => {
return (
<h3>
{name} - {msg}
</h3>
);
};
export default messageCheckHOC(MSG);
// file 2: message.js -- code end -->
// file 3 : App.js -- code start --->
import MSG from "./message.js";
export default function App() {
return (
<div className="App">
<h3>HOC COMPONENTS </h3>
<MSG name="Mac" msg="Heyy !!! " isAllowedToView={true} />
<MSG name="Robin" msg="Hello ! " isAllowedToView={true} />
<MSG name="Eyann" msg="How are you" isAllowedToView={false} />
</div>
);
}
// file 3 : App.js -- code end --->

Unable to use hook in HOC for functional component

Using HOC to render functional component ie. SampleComponent here does work for me.
const SampleComponent: FC = () => {
return (<div>Hello World</div>);
};
export default HOC({ component: SampleComponent });
And HOC is->
const HOC = ({ component: Component }) => {
return (() => <Component/>);
}
However I want to render this component conditionally , something like this-
<div>
{!id ? ( <SomeOtherComponent prop1={'hello'} prop2={'world'} /> ) : ( <Component /> )}
</div>
Here id is coming as a response from graphql query hook, which again i am unable to use in HOC function.
For starters, hooks are meant to be a replacement for HOC.
However, you can definitely use hooks in a HOC which is a function component. Only thing, you need to ensure is that you are using hook in a component that is rendered and not a function.
For instance use a hook like below is incorrect since you do not render HOC component but use it like const X = HOC(Component);
const HOC = ({ component: Component }) => {
const id = useQuery(query);
return (() => <Component/>);
}
The correct way to do it would be
const HOC = ({ component: Component }) => {
return () => {
const id = useQuery(query);
return (<div>
{!id ? ( <SomeOtherComponent prop1={'hello'} prop2={'world'} /> ) : ( <Component /> )}
</div>)
}
}
Working sample demo
However when you implement it like above, you are likely to receive a ESLINT warning because ESLINT is not intelligent enough yet to detect how the component is used.
You can disable the warning for such scenarios

Conditonal component still renders - ReactJS

I'm learning react and wanted to try creating a loading component that shows a loading text until a condition is met i.e. the props has the correct information.
The problem is that the element is still loading even if the condition is not met:
Leading Component:
import React from 'react';
const Loading = ({ condition, children }) => (<div>{condition ? children :
'Loading'}</div>);
export default Loading;
Here is my render method for a component that uses the Loading Component:
return
(<Loading condition={props.data && props.data.result && props.data.result.length > 1}>
<div> { ViewHelper.getCatalogItems(props.data) }</div></Loading>);
Now my problem is I'm getting an error when calling { ViewHelper.getCatalogItems(props.data) } becauuse props.data is undefined however I was hoping that the Loading Component wouldn't call the function if the ternary condition in the LoadingComponent was false.
if I change ViewHelper.getData to just some string value, everything seems to work and 'Loading ' is displayed.
Thanks
The fact that Loading component doesn't use children doesn't mean that children aren't rendered. Otherwise there would be nothing to pass as props.children to parent component.
As can be seen in this example, child expression is evaluated despite children prop ignored in parent component.
A proper way to handle this and prevent eager children rendering is to use render prop recipe, which is also known as function as a child:
const Loading = ({ condition, children }) => (
<div>{condition && children ? children() : 'Loading'}</div>
);
...
<Loading condition={props.data && props.data.result && props.data.result.length > 1}>
{() => (
<div> { ViewHelper.getCatalogItems(props.data) }</div>
)}
</Loading>
Notice that since props.children is a function, it's used as children() in ternary expression.
Or use a HOC for components:
const withLoading = (Comp) =>
({ condition, ...props }) => (
<div>{condition ? <Comp {...props} /> : 'Loading'}</div>
)
);
...
const LoadingCatalogItemsComponent = withLoading(CatalogItemsComponent);
the children parameter received in the Loading component will have already rendered in the parent (try logging the content of children from Loading)
getCatalogItems will be called regardless of the conditional
cases where props.data need to be handled here. there are multiple ways to do this:
defaultProps or type checking in general
input validation for getCatalogItems
destructuring assignment
default function parameters
Updated answer: as estus points out below, render props are probably a good way to do this. Here's an example:
import React from "react";
import ReactDOM from "react-dom";
const Loading = ({ condition, render }) => {
if (condition) {
return render();
} else {
return "Loading";
}
};
const Thing = ({ data }) => {
console.log(data);
return data.map(d => <li>{d}</li>);
};
class App extends React.Component {
state = {
data: [1, 2, 3]
};
render() {
return (
<Loading
condition={this.state.data.length > 1}
render={() => {
return <Thing data={this.state.data} />;
}}
/>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CodeSandbox here.
As #estus said, HOCs and render props are two popular methods. Moving:
<div>{ ViewHelper.getCatalogItems(props.data)}</div>
into its own component (which is passes the prop, e.g., <CatalogItems {...props} />) will also stop you from getting errors. As long as the code isn't in the actual render method; otherwise, it's fired, regardless of whether React would have actually rendered it.
Example:
const Loading = ({ condition, children }) => (
<div>{condition ? children : "Loading in 3 seconds"}</div>
);
// now that it's in its own component the code isn't run until the component actually renders
const CatalogItems = ({ data }) => data.result.map(item => item);
class App extends React.Component {
state = {
data: null
};
// dummy API call
componentDidMount() {
setTimeout(
() => this.setState({ data: { result: ["cat ", "dog ", "mouse "] } }),
3000
);
}
render() {
const props = this.state; // let's just pretend these were inherited props
return (
<Loading
condition={
props.data && props.data.result && props.data.result.length > 1
}
data={props.data}
>
<CatalogItems {...props} />
</Loading>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<div id='root'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.3.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.3.1/umd/react-dom.development.js"></script>

Resources