how to handle props between components in react - reactjs

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.

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 {
...
}
}

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

How to use context within same component file using react and typescript?

I want to access the state from one component to another. To do so i want to wrap contextprovider only to the component where state changes on clicking a button and return state from a usehook so that another component can access the state.
below is how the componnet looks without context applied,
function UploadButton () { //this is where state is set
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
const handleClick = () => {
setIsDialogOpen(!isDialogOpen);
}
return (
<>
<Button onClick={handleClick}/>
{isDialogOpen && <Upload/>}
</>
);
}
function UserButton() { //this is where state is accessed
return (
<Icon/> //this icon should be displayed only if !isDialogOpen
);
}
With context looks like below, I have DialogContext within same file where UploadButton is.
interface DialogCtxState {
isDialogOpen: boolean;
setIsDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
}
const initialDialogState: DialogCtxState = {
isDialogOpen: false,
setIsDialogOpen: () => {},
};
const DialogContext = React.createContext<DialogCtxState>(
initialDialogState
);
export const DialogContextProvider: React.FC = ({ children }) => {
const [isDialogOpen, setIsDialogOpen] = React.useState<boolean>(false);
return (
<DialogContext.Provider
value={{
isDialogOpen,
setIsDialogOpen,
}}
>
{children}
</DialogContext.Provider>
);
}
function UploadButton () {
const {isDialogOpen, setIsDialogOpen} = React.useContext(DialogContext);
const handleClick = () => {
setIsDialogOpen(!isDialogOpen);
console.log('isDialogOpen', isDialogOpen) //prints false here.
}
return (
<DialogContextProvider>
<>
<Button onClick={handleClick}/>
{isDialogOpen && <Upload/>} //this doesnt render on clicking button as isDialogOpen
//is false
</>
</DialogContextProvider>
);
}
The above snippet doesn't render the Upload component as isDialogOpen is always false no matter if I click the button.
I am not knowing what is wrong. could someone help me with this? thanks.
The issue is that you need to move your context provider one level higher in your react components in order to use useContext in you UploadButton component. You cannot place the context provider inside of UploadButton in order to use it's context. You need to move the provider into a parent component.
Here's a codesandbox demostrating it.

React child component does not re-render when props passed in from parent changes

I have a simplified react structure as below where I expect MyGrandChildComponent to re-render based on changes to the 'list' property of MyParentComponent. I can see the list take new value in MyParentComponent and MyChildComponent. However, it doesnt even hit the return function of MyGrandChildComponent. Am i missing something here?
const MyGrandChildComponent = (props) => {
return (
<div>props.list.listName</div>
);
};
const MyChildComponent = (props) => {
return (
<div><MyGrandChildComponent list={props.list}/></div>
);
}
const MyParentComponent = (props) => {
const list = { listName: 'MyList' };
return (
<div><MyChildComponent list={list} /></div>
);
}
In your MyParentComponent, the list is not a state variable and as such changing it will not even cause a re-render. If you absolutely want that when ever you change the value of list it re renders, then you will want to bring state to your functional component and the way to do that is to use hooks.
In this case your parent component will be something like below
import React, {useState} from 'react'
const MyParentComponent = (props) => {
const [list, setList] = useState({ listName: 'MyList' });
return (
<div><MyChildComponent list={list} /></div>
);
}
then at the child component you render it as I suggested in the comment above.
The parent needs to hold the list as a state variable and not just as a local variable. This is because react rerenders based on a state or prop change and at the parent you can only hold it in the state. With this when the value of list changes there will be a re-render which will then propergate the change to the children and grandchildren.
Also the only way of maintaining state in a functional component is to use hooks.
const MyGrandChildComponent = (props) => {
return (
<div>{props.list.listName}</div>
);
};
You forgot the {} around props.list.listName

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

Resources