after getting all mixed up with state, i am now trying to restructure my app in a way that might be more reflective of best practices (not sure if this is the way, advice is welcome.)
so, i have my main page, which holds 3 states: viewer,buyside,sellside
there are also three different components, one for each of those states.
i want to be able to pass the props down from the main page, through those components, to their children (i've read this is the best approach??)
main page:
//we have 3 states for the website: viewer,buyside customer, sellside customer
const [visitorType, setVisitorType] = useState('viewer');
if (visitorType == 'viewer') {
return(
<div>
<Viewer visitortype='viewer' setvisitor={()=>setVisitorType()}/>
</div>
)}
else if (visitorType =='buyside') {
return(
<div>
<Buyside visitortype='buyside' setvisitor={()=>setVisitorType()}/>
</div>
)}
else if (visitorType =='sellside') {
return(
<div>
<Sellside visitortype='sellside' setvisitor={()=>setVisitorType()}/>
</div>
)}
};
what is the best way to pass down the main page props, so that i can bring them down to any grandchildren, along with the child props?
the viewer component -UPDATED-:
const MainView = (props) => {
return(
<>
<Navbar mainprops={{props}}/>
</>
)
};
export default MainView
i was previously just passing them individually, but realized it might be better to do so as one object...
UPDATE: point taken on the syntax, but i'm wondering how i can best pass the objects
nav component (grandchild)
const Navbar = (props) => {
const {mainprops} = props.mainprops;
if (mainprops.visitortype == 'viewer') {
return(
<>
<h1>viewer navbar</h1>
</>
)}
else if (mainprops.visitortype =='buyside') {
return(
<>
<h1>buyside navbar</h1>
</>
)}
else if (mainprops.visitortype =='sellside') {
return(
<>
<h1>sellside navbar</h1>
</>
)}
};
export default Navbar;
UPDATE 2 - this works, but not sure if it is the correct way, are these still considered object literals??
viewer component:
const MainView = (props) => {
const mainprops = {...props}
return(
<>
<Navbar mainprops={mainprops}/>
</>
)
};
export default MainView
navbar component
const Navbar = (props) => {
const mainprops = {...props.mainprops};
if (mainprops.visitortype == 'viewer') {
return(
<>
<h1>viewer navbar</h1>
</>
)}
else if (mainprops.visitortype =='buyside') {
return(
<>
<h1>buyside navbar</h1>
</>
)}
else if (mainprops.visitortype =='sellside') {
return(
<>
<h1>sellside navbar</h1>
</>
)}
};
export default Navbar;
if this is correct, then is this what #amir meant?
First there are certain rules for passing props:
You never ever pass literal object as props since it will not be the same every re-render and will cause the child component to re-render too (without any new info)
You don't need to do that
<Viewer visitortype='viewer' setvisitor={()=>setVisitorType()}/>
You can:
<Viewer visitortype='viewer' setvisitor={setVisitorType}/>
since it comes from useState react make sure the setVisitorType keeps the same reference
And now for you error, you almost correct you just did a js syntax error
you should write it like this:
const MainView = (props) => {
return(
<>
<Navbar mainobj={{
visitortype:props.visitortype,
setvisitor:props.setvisitor
}}
/>
</>
)
};
export default MainView
But again you never send literal object as props
I would keep it inside a ref or state (depend if the visitor state will be change)
Related
I'm trying to find a way to switch between dark and light theme in my web app.
I want to add the switch in my AppBar component which is inside my header but I'm not sure how to get it to work on all of the web app and not just the header.
AppBar.js :
//imports ...
const AppBar = () =>{
const [theme, setTheme] = useState(false);
const changeTheme = () => {
setTheme(!theme);
};
//rest of the code....
<Box sx={{ flexGrow: 0 }}>
<Switch checked={theme} onChange={changeTheme} />
</Box>
};
export default AppBar;
here is the code from the Header.js
const Header = () => {
return (
<header>
<AppBar />
</header>
);
};
export default Header;
so the header is just rendering one component one could get rid of it if it was necessary.
and here is my App.js (routes)
//imports ...
//themes ...
const app = ()=>{
return (
<React.Fragment>
<ThemeProvider theme={theme ? lightTheme : darkTheme}>
<CssBaseline />
<Router>
<Header />
<Routes>
//Routes ...
</Routes>
<Footer />
</Router>
</ThemeProvider>
</React.Fragment>
);
}
export default App;
I'd really appreciate the help and Thanks in advance!
you can store the value of theme inside context or redux store and change it using a dispatch function once that value changes the whole component related to it will re render !, so your component will change the value inside context ! rather than having the value stuck inside one component.
Experts am in the learning stage and I wanted to know how can I can get the text input value from the search bar and pass it to my another component where I have an API URL as a search query.
I have seen many videos which use props and I guess that’s the correct way if not wrong to send the data between components. However, I tried to use that but I have an issue. I can not see the props value which I have passed from my nav component (textfiled value) to the product component.
any help how can I do that.
Please if you can provide a source code explanation would be great.
Also please find the relevant code below:
Please see the full code here https://codesandbox.io/s/determined-nightingale-m2mtn?file=/src/App.js
This is the code on my nav component and I want the onchange value to be passed to the product component.
const Navbar = () =>{
const[searchText, setSearchText] = useState();
const handleChange = (event) =>{
setSearchText(event.target.value)
}
return (
<>
<div className="nav_main">
<div className="logo_main">
<Link exact to='/home' className="logo" > Movie </Link>
</div>
<div className="search_main">
<TextField
id="input-with-icon-textfield"
className= "searchTextStyle"
size= "small"
placeholder="search"
variant="standard"
value = {searchText}
onChange = {handleChange}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon style={{color:'white'}} />
</InputAdornment>
),
}}
/>
</div>
<div className="nav_menu">
<NavLink exact activeClassName= "active_link" to='/home' className="navitems" > Home </NavLink>
<NavLink exact activeClassName= "active_link" to='/about'className="navitems" > About </NavLink>
<NavLink exact activeClassName= "active_link" to = '/products' className="navitems" > Products </NavLink>
<IconButton className="account_icon">
<AccountCircleIcon fontSize="small"/>
</IconButton>
</div>
</div>
</>
)
};
export default Navbar;
The Product component:
const Produts = (props) =>{
console.log(props.value);
return (
<>
<h1>{`http://api.themoviedb.org/3/search/movie?api_key=Mykey&query=${props.value}`}</h1>
</>
)
};
export default Produts;
Thanks
how are you?
What happens is, it's okay, you are getting the data from de input, but you are not passing it forward to the other component where you want to use that data.
How can you do that?
As they are 'sibling' components, i recommend you to put the handleChange and the useState where you are going to save the data in the FATHER component, pass them through props for the component where the user types, and then pass the stored data also via props for the component where you are going to use it.
Let me give you a pratical example of how you can do it:
On the App you handle the state and provide it for the components via props.
function App() {
const [searchText, setSearchText] = useState();
const handleChange = (event) => {
setSearchText(event.target.value);
};
return (
<>
<Navbar handleChange={handleChange} searchText={searchText}/>
<Switch>
<Route path="/products"
render= { (props) => <Produts {...props} searchText={searchText} /> }
/>
</Switch>
</>
);
}
in the NavBar component you get them via props, destructuring it:
const Navbar = ({handleChange, searchText}) => {
return (
<>
<div className="nav_main">
<div className="logo_main">
<Link exact to="/home" className="logo">
{" "}
Movie{" "}
</Link>
</div>
and in the Produts component you get them also via props:
const Produts = (props) => {
console.log(props.searchText);
const classes = useStyles();
const [movieData, setMovieData] = useState([
// the below is an fake api data which needs to be deleted when final disgn is ready and add real api from useEffect
{
adult: false,
observe that, before you were getting "props.value" but the name that you get in the component is the same name that you used to pass it from the provider component, in this case 'searchText'.
tl;dr
You need to pass them via props, choosing a name, and you get them from the other side using the same name. To get them you can either use 'props.PROP_NAME' or you can use the destructuring method and get the name directly.
Also, to pass a props through a Router rendered component you have to use this syntax:
render= { (props) => <Produts {...props} PROP_NAME={VARIABLE_YOU_ARE_USING}
On the NavBar and the Produts components i used different methods so you can see that you can use both for getting the props information.
I hope that can help, anyway i'm available for further explanations.
The NavLink has additional properties which can be used. The 'to' attribute needn't be only a string value. For example:
<NavLink exact activeClassName= "active_link" to = '/products' className="navitems" > Products </NavLink>
can be rewritten as
to={{
pathName: '/products',
someProps:{data: 'Place any Data here'}
}}
className="navitems" > Products </NavLink>
Then in your products component you should be able to retrieve the data using this.props.location.someProps.data.
Take a look at these articles:
https://reactrouter.com/web/api/Link/to-object,
https://medium.com/#bopaiahmd.mca/how-to-pass-props-using-link-and-navlink-in-react-router-v4-75dc1d9507b4
They outline exactly how this should be used.
I have difficulties trying to pass props to this.props.children. I know there's a few similar posts, however, I believe I have tried most of the accepted solutions, and it's still not behaving and expected. So, I guess I'm missing something vital.
The general idea is this: I have a <NavBar> component that I would like to wrap around my pages as shown below. I'd like for the wrapped page to accept props passed down from the <NavBar> component.
<NavBar>
<Container>
<Grid container>
<Grid item>
...
</Grid>
</Grid>
</Container>
</NavBar>
Currently my <NavBar> is defined as such:
class NavBar extends React.Component<React.PropsWithChildren<NavBarProps>, NavBarState>
So, my component has a prop children?: React.ReactNode. In my render() method I am rendering an <AppBar> (from Material UI library) underneath which I display the children similar as such:
render() {
const {children} = this.props;
return(
<>
<AppBar>...</AppBar>
{children}
</>
)
}
Some attempts I've had:
render() {
const children = React.cloneElement(this.props.children as React.ReactElement, {
test: "test"
});
return(
<>
<AppBar>...</AppBar>
{children}
</>
)
}
What I expect: In this case, I would like to be able to access the test props from any page wrapped within <NavBar> like this.props.test
I also tried:
const children = React.Children.map(this.props.children as React.ReactElement, (child) =>
React.cloneElement(child, { test: "test" })
);
&
const children = React.Children.map<ReactNode, ReactNode>(this.props.children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { test: "test" });
}
});
Result so far: I've been unsuccessful and trying to access this.props.test from my page returns undefined.
I don't see anything wrong with your third attempt. Here is a working example using that method. Notice unlike your second attempt, you do need to return from the map.
function Test() {
return (
<Parent>
<Child />
</Parent>
);
}
class Parent extends React.Component {
render() {
const children = React.Children.map(this.props.children, (child) => {
return React.cloneElement(child, {test: 'test'});
});
return (
<div>
<h3>Parent</h3>
{children}
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>
<h3>Child</h3>
Test Prop: {this.props.test}
</div>
);
}
}
ReactDOM.render(<Test/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
i want to display a dialog on clicking additem and addbooks button using react and typescript.
what i am trying to do?
I want to display a dialog on clicking additem button or addbooks button . this dialog will have hide button. on clicking this hide button the dialog should never appear again for the session.
Below is the code,
function MainComponent () {
const [showDialog, setShowDialog] = React.useState(false);
const openDialog = () => {
setShowDialog(true);
};
const hideDialog = () => {
setShowDialog(false);
};
return (
<Route
path="/items"
render={routeProps => (
<Layout>
<Home
showDialog={showDialog}
openDialog={openDialog}
hideDialog={hideDialog}
{...routeProps}
/>
{showDialog && (
<Dialog
hideDialog={hideDialog}
/>
)}
</Layout>
)}
/>
<Route
path="/items/:Id/books/:bookId"
render={routeProps => (
<Layout>
<Books
openDialog={openDialog}
{...routeProps}
/>
{showDialog && (
<Dialog
hideDialog={hideDialog}
/>
)}
</Layout>
)}
</>
)
function Home ({openDialog}: Props) {
return (
<button Onclick={openDialog}>AddItem</Button>
)
}
function Books ({openDialog}: Props){
return (
<button onClick={openDialog}>AddBooks</Button>
)
}
function MessageDialog({hideDialog}: Props) {
return (
<button onClick={hideDialog}>hide</button>
)
}
Now the question is as you see i am rendering MessageDialog in two places based on showDialog value. if users clicks additems button the dialog is displayed and when user to navigates to other view and clicks addbooks button the dialog is displayed.
somehow i feel this is not the right approach or something is missing...
How can i create a global dialog component that is accessible anywhere from my app or using toastify or some better approach. could someone help me with this. thanks.
I think your code is pretty good, but I can think of two minor things that could potentially improve it.
Firstly, you have some duplicate code in your main component. You should be able to move the Dialog outside of the routes. Like so:
return (<>
{showDialog && (
<Dialog
hideDialog={hideDialog}
/>
)}
<Route ... />
<Route ... />
</>)
This will render your dialog no matter which route is matched.
Secondly, instead of passing openDialog and hideDialog callbacks as props, you could create a React context. This is optional, and dependning on the case this might not be desired. A React context is slightly more complex than just a callback, so it adds complexity to your code. The benefit is that the callbacks doesn't need to be passed down through the props of every child component, which makes the component structure cleaner if you have a large component tree.
To create a React context for this use case, you could do the following:
// Create a context with a default value of false
let DialogContext = React.createContext()
function MainComponent () {
...
return (<DialogContext.Provider value={{ setDialogOpen: (open) => setShowDialog(open) }}>
{showDialog && (
<Dialog />
)}
<Route ... />
<Route ... />
</ DialogContext.Provider>)
}
function Home ({openDialog}: Props) {
let dialogContext= useContext(DialogContext)
return (
<button onclick={() => dialogContext.setDialogOpen(true)}>AddItem</Button>
)
}
function Books ({openDialog}: Props){
let contextValue = useContext(DialogContext)
return (
<button onClick={() => dialogContext.setDialogOpen(true)}>AddBooks</Button>
)
}
function MessageDialog({hideDialog}: Props) {
let dialogContext= useContext(DialogContext)
return (
<button onClick={() => dialogContext.setDialogOpen(false)}>hide</button>
)
}
I have a React component as shown. I am passing prop hasItems and based on this boolean value, i am showing PaymentMessage Component or showing AddItemsMessage component.
export const PayComponent = ({
hasItems
}: props) => {
return (
<Wrapper>
{hasItems ? (
<PaymentMessage />
) : (
<AddItemsMessage />
)}
<Alerts
errors={errors}
/>
</Wrapper>
);
};
This works well. Now, i need to pass another prop (paymentError). So based on this, i modify the JSX as below. I will highlight the parts i am adding by using comment section so it becomes easy to see.
export const PayComponent = ({
hasItems,
paymentError //-----> added this
}: props) => {
return (
<Wrapper>
{!paymentError ? ( //----> added this. This line of code errors out
{hasItems ? (
<PaymentMessage />
) : (
<AddItemsMessage />
)}
):( //-----> added this
<Alerts
errors={errors}
/>
) //-----> added this
</Wrapper>
);
};
Basically, i am taking one more input prop and modifying the way my JSX should look. But in this case, i am not able to add one boolean comparison one after the error. How do i make it working in this case. Any suggestions please ???
I recommend you to create a function to handle this behavior. It's easier to read and to mantain
export const PayComponent = ({
hasItems,
paymentError
}: props) => {
const RenderMessage = () => {
if (hasItems) {
if (paymentError) {
return <PaymentMessage />
}
return <AddItemsMessage />
}
return <Alerts errors={errors}/>
};
return (
<Wrapper>
<RenderMessage />
</Wrapper>
);
};