React HoC - props are not passed to wrapped component - reactjs

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 --->

Related

useContext - Child component loses state after parent state is changed

Here is a pseudo code example of my app structure. I am trying to share state globally with react context, but updating state at the top level is causing issues with Child components re-rendering and resetting state (I think).
// My top level where I store the state all other components need
function App() {
const [userData, setUserData] = useState()
const userContext = {
userData,
setUserData
}
return (
<App>
<Context.Provider value={userContext}>
<Child />
<Child />
<Child />
</Context.Context.Provider>
</App>
)
}
// My child component where I want to preserve state
const Child = () => {
const [childState, setChildState] = useState('default value')
// I want to keep this value
setChildState('new value')
// This is causing App.js to re-render, then Child to rerender, and I lose child state. Then on the next render my 'childState' is back to 'default value', when I want it to still be 'new value'
const userContext = useContext(...)
userContext.setUserData('some change to userdata')
return {
...
}
}
My questions:
Is this an okay app structure, or is there a problem with it? Is there something I can do to persist the state in the Child componenet, or do I need to move the shared state out of App.js somehow?
You have structured the useContext wrong. You can check in React hooks how to use it.
One example with full-functionality which it may help you is:
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
const [userData, setUserData]=useState('nothing here')
// any code you want to pass in the code
//e.g. a function
const randomFunction = ()=>{
//do something here
}
return (
<AppContext.Provider
value={{
userData,
setUserData,
randomFunction
}}
>
{children}
</AppContext.Provider>
);
};
export const useGlobalContext = () => {
return useContext(AppContext);
};
export { AppContext, AppProvider };
then all you have to do is to wrap all the components (children) you want, e.g. wrap <App /> so, more or less everything:
<AppProvider>
<App />
</AppProvider>
So now in this case you can use everything from your AppContext in all your code, you can pass more variables and functions if you want, and you import that by using:
import { useGlobalContext } from '/pathYouHaveIt';
function App() {
const {
userData,
setUserData,
randomFunction,
} = useGlobalContext();
// now you can use those like you have them set-up in the App()
Provide state from parent to child like props, it wood be greater than useContext.
function App() {
const [userData, setUserData] = useState()
return (
<div className="App">
<Child userData={userData}, setUserData={setUserData}/>
</div >
)
}
And the child component has the form
const Child = ({userData, setUserData}) => {
const [childState, setChildState] = useState('default value')
setChildState('new value')
setUserData('some change to userdata')
return {
...
}
}

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.

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

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

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

How do I wrap this child in an Higher Order Component?

const LoggedInView = (props) => <Text>You are logged in!</Text>
export default withAuth(LoggedInView)
const withAuth = (component) => <AuthRequired>{ component }</AuthRequired>
const AuthRequired = (props) => {
const context = useContext(AuthContext)
if(!context.auth){
return (
<View style={styles.container}>
<Text>You need to login . Click here</Text>
</View>
)
}
return props.children
}
My <AuthRequired> view works fine, but my withAuth does not.
HOCs take a component and return another component. You're taking a component and returning a React node, not a component. Docs reference
In your case, you should be able to do something like:
const withAuth = (Component) => (props) => <AuthRequired><Component ...props /></AuthRequired>
It might be easier to understand as:
function withAuth(Component) {
return function WithAuthHOC(props) {
return (
<AuthRequired>
<Component ...props />
</AuthRequired>
);
}
}
You can use Auxiliary component. It is a higher order component. Auxiliary element is something that does not have semantic purpose but exist for the purpose of grouping elements, styling, etc. Just create a component named Aux.js and put this on it:
const aux = ( props ) => props.children;
export default aux;
Then wrap withAuth with Aux.
const withAuth = (component) => {
return (
<Aux>
<AuthRequired>{ component }</AuthRequired>
</Aux>
);
}
As I know, you cannot return a function as children in React. How about you trying this?
const LoggedInView = <div>You are logged in!</div>;
This code works when I tried on my laptop.
Please, take a look at this link: Functions are not valid as a React child. This may happen if you return a Component instead of from render

Resources