Best way to render multiple child components - javascript

I just started learning ReactJS and am curious to know the best practice to rendering multiple different child components. What is the right way and most efficient way to do so? This is my current render function;
render() {
return (
<div>
<Display
image={this.state.professional.image}
username={this.state.professional.username}
/>
<div className="container">
<Navbar
brand_name={!!this.state.professional.brand_name}
username={!!this.state.professional.username}
description={!!this.state.professional.description}
menu={!!this.state.professional.menu}
reviews={!!this.state.reviews}
photos={!!this.state.professional.photos}
email={!!this.state.professional.email}
address={!!this.state.professional.service_addresses}
/>
<section className="artist-page">
<div className="container">
<About
description={this.state.professional.description}
accolades={this.state.professional.accolades}
cancellation={this.state.professional.lead_time_cancellation}
rules={this.state.professional.service_rules}
faqs={this.state.professional.faqs}
/>
{(this.state.professional.menu || this.state.professional.offers)
&&
<Services
services={this.state.professional.menu}
offers={this.state.offers}
/>}
{!!this.state.photos && <Portfolio photos={this.state.photos} />}
{!!this.state.reviews && <Reviews reviews={this.state.reviews} score={this.state.professional.rating_overall_stats} count={this.state.professional.review_count} />}
{(!!this.state.professional.email || !!this.state.professional.address) && <Contact
address={this.state.professional.service_addresses[0]}
name={!!this.state.professional.brand_name ? this.state.professional.brand_name : this.state.professional.username}
/>}
</div>
</section>
</div>
</div>
);
}

Think about what needs to be a state and what should be props.
It seems like this parent component that you pasted here is storing information of all the children as state, before passing down to its children as props.
Are you sure this parent needs to know anything about username, description, menu, etc of <Navbar>? If it doesn't, then save them as Navbar's own states.
You can pass down entire this.state.professional object as props.
<About professional = {this.state.professional}/>
Then in your About component, you can take apart the object and use it accordingly:
<p>{ this.props.professional.description }</p>
You can consider destructuring for readability
const {description, rules} = this.state.professional
return (
<About description={description} rules={rules}/>
)
Fix your <Navbar>
Personally I think that it looks weird and unreadable when you pass down boolean like that. You can consider this:
<Navbar reviews={this.state.reviews} {...this.state.professional} />
Then do your boolean evaluation inside Navbar component itself, here's a few example:
const {username, description, reviews} = this.props
<p>{ reviews || 'No reviews' }</p>
<p>{ username ? formatName(username) : "" }</p>
<p>{ description && 'Has description!' }</p>

Related

Functions are not valid as react child Error while using Navigate in react router dom

I am getting the error ("Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.") when I am trying to use condition which includes Navigate to "/path" (look at the statement in the code).
return (
<>
<Router>
<Navbar bg="dark" variant="dark">
<Container>
<Navbar.Brand to="/">MoviesReviews</Navbar.Brand>
<Nav className="me-auto">
<Link to="/picks">Picks</Link>
<Link to="/critics">Critics</Link>
</Nav>
<Form className="d-flex">
<FormControl
type="search"
placeholder="Search"
className="me-2"
aria-label="Search"
id = "SearchBox"
/>
{redirect === true? (() => (<Navigate to = "/search"/>)) : null}
<Button variant="outline-success" onClick = {() => pressSearch()}>Search</Button>
</Form>
</Container>
</Navbar>
<Routes>
<Route path = "/" element = {<Reviews/>}/>
<Route exact path = "/picks" element = {<Reviews/>}/>
<Route exact path = "/critics" element = {<Critics/>}/>
<Route exact path = "/search" element = {<SearchReviews search = {searchString}/>}/>
</Routes>
</Router>
</>
);
If you look at the Route exact path for "/search", you see that I am actually returning a <Component/>. I am pretty sure the problem is in the line which contains the condition ({redirect === true? (() => (<Navigate to = "/search"/>)) : null}).
{redirect === true? (() => (<Navigate to = "/search"/>)) : null} with the above statement you :
in false case you are returning null.
in true case you are trying to render a function.
() => (<Navigate to = "/search"/>)
Hence it is not valid as a react child.
You have to do : {redirect === true ? <Navigate to="/search"/> : null} as mentioned in comments.
Functions are not valid JSX, they can't be rendered.
If you are conditionally rendering React components there are a couple syntaxes you can use, neither involves using a function. It's considered bad practice to compare boolean variables against true/false, just use the variable's value for the condition. If you just need to test the truthy/falsey-ness of a value then use !! to coerce it to a strict boolean value.
Using a ternary: condition ? <Component1 /> : null
{redirect ? <Navigate to="/search" replace /> : null}
This is useful if you need to conditionally render one of two different components, or to return null as a component result.
Using logical AND (&&): condition && <Component1 />
{redirect && <Navigate to="/search" replace />}
This is useful if you only need to conditionally render a single component.
If you are conditionally rendering the entire component render result then you should use the first in order to return null as valid JSX.
Suggestion
It would be better to issue an imperative redirect instead of setting any redirect state. This has the benefit of not requiring an additional React render cycle to effect the change.
In order for this component to use any react-router-dom hooks, the Router will need to be moved higher in the ReactTree, i.e. to the parent component or higher of this component.
Example:
import { useNavigate } from 'react-router-dom';
...
const Component = () => {
const navigate = useNavigate();
...
// logic to replace setting any `redirect` state
if (<condition to redirect>) {
navigate("/search", { replace: true });
}
...
return (
<>
<Navbar bg="dark" variant="dark">
<Container>
<Navbar.Brand to="/">MoviesReviews</Navbar.Brand>
<Nav className="me-auto">
<Link to="/picks">Picks</Link>
<Link to="/critics">Critics</Link>
</Nav>
<Form className="d-flex">
<FormControl
type="search"
placeholder="Search"
className="me-2"
aria-label="Search"
id = "SearchBox"
/>
<Button
variant="outline-success"
onClick={pressSearch}
>
Search
</Button>
</Form>
</Container>
</Navbar>
<Routes>
<Route path="/" element={<Reviews />} />
<Route path="/picks" element={<Reviews />} />
<Route path="/critics" element={<Critics />} />
<Route path="/search" element={<SearchReviews search={searchString} />} />
</Routes>
</>
);
};

React - Toggle theme from another component

I am working on a project on react but I have ran into an issue.
The issue I am having is that I need to be able to toggle my "dark" and "light" theme from a icon that is in a different component. This icon exists in my BottomNavigation but the function for switching the theme exists in my app.js.
Did some research on my own and found that I need to "lift the state up". Issue is that I need to lift it twice as my files look like this:
./Components
./Home.js
- (components gets added here)
./Navigation
./BottomNavigation.js
app.js
(/home is added here)
My app.js looks like:
function App() {
const [theme, setTheme] = useState("light");
const themeToggler = () => {
theme === "light" ? setTheme("dark") : setTheme("light");
};
return (
<ThemeProvider theme={theme === "light" ? lightTheme : darkTheme}>
<GlobalStyles />
<Router>
<Route exact path="/">
<Home />
</Route>
<Route exact path="/account">
<Account />
</Route>
</Router>
</ThemeProvider>
);
}
My Home.js looks like:
const Home = (props) => {
const [showingState, setIsShowing] = useState(false);
return (
<div>
<TopNavigation isShowing={(showingState) => setIsShowing(showingState)} />
<BottomNavigation />
<ImageSlider />
<Grid />
{showingState && (
<CurrencyPopup
isShowing={(showingState) => setIsShowing(showingState)}
/>
)}
<BestSeller />
<CollectionPromo />
<Instagram />
<Footer />
</div>
);
};
My BottomNavigation.js looks like (only took the part with the icon):
<div className={classes.options_container}>
<IconApp className={classes.icon_container}>
<span className={classes.cart_sum}>$0.00</span>
<Cart className={classes.icon} />
</IconApp>
<IconApp className={classes.icon_container}>
<Heart className={classes.icon} />
</IconApp>
<IconApp className={classes.icon_container}>
<Visibility
onClick={() => props.setTheme("")} //This icon shall switch the theme
className={classes.icon}
/>
</IconApp>
<IconApp className={classes.icon_container}>
<a href="/account">
<User className={classes.icon} />
</a>
</IconApp>
</div>
If you have any ideas or need something more from the code, let me know!
For best practice, you have a few options:
Use React Context.
Use a state management library like Redux and MobX.
You definitely don't want to life state up in your case, because the two components are too far away from each other in the hierarchy.
Lifting state up would be solution that does not require any additional knowledge.
However, its excessive use (over three or more component) is a bad practice, since it makes code dirty
Using RecoilJS would be easiest, since its api is nearly identical to that of useState, and there is less work for you to do
Sticking to vanilla ReactJS, try React Context

React Router. Why, when changing the route, the component is rendered 2 times, which causes 2 requests to the server?

I am using ReactTransitionGroup with ReactRouter.
The goal is to reroute smoothly from one component to another. The problem - is that the component is rendered twice.
An example of a component (view) that renders twice
I am using the console to check. You might say that this is not critical. But, the problem is that because of this problem, 2 requests go to the server (one extra). Therefore, it is desirable for me to get rid of this bug.
This is the component itself - the switch
When switching a route, the console issues logs twice
I need to figure out why the side effect is being called twice. If there is not enough information, then write comments. I will try to answer as quickly as possible.
Here's an example from the documentation. I have achieved this effect, but the problem has already been described.
UPD: I remember very well that once it worked like a clockwork. But, probably, I myself did not notice that I changed something, which led to this problem.
UPD: If you need a code, then please, the required elements:
const TabList = ({ tabs }) => {
return (
<nav className="p-my-company__tabs">
{tabs.map(({ to, label, id }) => (
<NavLink to={to} key={id}>
<div>{label}</div>
</NavLink>
))}
</nav>
);
};
const TabViews = ({ tabViews }) => {
const location = useLocation();
return (
<div className="p-my-company__views">
<TransitionGroup>
<SwitchTransition mode="out-in">
<CSSTransition
key={location.pathname}
classNames={{
enter: 'move-enter',
enterActive: 'move-enter-active',
exit: 'move-exit',
}}
timeout={100}>
<Switch>
{tabViews.map(({ path, Component, id }) => (
<Route path={path} render={() => <Component />} key={id} />
))}
</Switch>
</CSSTransition>
</SwitchTransition>
</TransitionGroup>
</div>
);
};
<div className="p-my-company__panel">
<TabList
tabs={[
{ to: ROUTES.COMMON_INFO, label: 'Общая информация', id: 1 },
{ to: ROUTES.SHOWCASE, label: 'Моя витрина', id: 2 },
]}
/>
<TabViews
tabViews={[
{ path: ROUTES.COMMON_INFO, Component: CommonView, id: 1 },
{ path: ROUTES.SHOWCASE, Component: ShowCaseView, id: 2 },
]}
/>
</div>
const ShowCase = () => {
useEffect(() => {
console.log(2);
}, []);
return <div>ShowCase</div>;
};
Looks like the Switch component from React Router and React Transition Group don't work well together. The docs recommend avoiding the usage of the Switch component and passing a function to the Route's children prop. Since the function will be called regardless of whether there is a match or not, you can conditionally render Component if there's one.
<>
{tabViews.map(({ path, Component }) => (
<Route exact path={path} key={path}>
{({ match }) => (
<TransitionGroup>
<SwitchTransition mode="out-in">
<CSSTransition
in={match != null}
classNames={{
enter: 'move-enter',
enterActive: 'move-enter-active',
exit: 'move-exit',
}}
timeout={100}
unmountOnExit
key={location.pathname}
>
<div className="page">{match && <Component />}</div>
</CSSTransition>
</SwitchTransition>
</TransitionGroup>
)}
</Route>
))}
</>

Render multiple components with a single ternary operator

If currentProfiles.length > 0, I'd like to map over an array named profiles and render a profile component for each profile, and render a pagination component below the profiles. I tried this with a single ternary operator, but this results in only the pagination component being rendered.
{currentProfiles.length > 0 ? (
(currentProfiles.map(profile => (
<ProfileItem key={profile._id} profile={profile} />
)),
(
<Pagination
profilesPerPage={profilesPerPage}
totalProfiles={profiles.length}
/>
))
) : (
<Spinner />
)}
If I use two separate ternary operators, I get the list of profiles and pagination as expected, but can I do both things with a single conditional operator?
Your code just needs some restructuring. If you wrap the mapped profiles and pagination components in a parent fragment or other element, it's easy. Note, too, that the first example below still retains the ternary, as requested.
return (
<div className="App">
{currentProfiles.length ? (
<>
{currentProfiles.map(p => (
<Profile {...p} />
))}
<Pagination profilesPerPage={2} totalProfiles={totalProfiles} />
</>
) : (
<p>Loading...</p>
)}
</div>
);
However, you have a few options aside from wrapping them in a non-rendered Fragment or its shorthand derivative. You could also use an actual element, such as a div. Or even omit the parent entirely and place your logic within an array, as in:
<div className="App">
{currentProfiles.length ? [
currentProfiles.map(p => (
<Profile {...p} />
)),
<Pagination profilesPerPage={2} totalProfiles={totalProfiles} />
] : <p>Loading...</p>}
</div>
Always remember that, unless you utilize the second approach, you'll need to ensure siblings share a common parent.
Working example.
You can use an array or a fragment https://reactjs.org/docs/fragments.html
{currentProfiles.length > 0 ? (
<>
currentProfiles.map(profile => (
<ProfileItem key={profile._id} profile={profile} />
)
<Pagination
profilesPerPage={profilesPerPage}
totalProfiles={profiles.length}
/>
</>
) : (
<Spinner />
)}

React conditional rendering of multiple child components

I'm trying to render multiple child components depending on state however I'm only able to return one child component (SyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag)
Each child component passes the same props, how could this code be kept DRY?
Works
export default ({changeState, myState, handleClick}) => (
<Navigation>
<span>Navigation</span>
<button onClick={() => changeState()}>Navigation</button>
{ myState ?
<NavigationItem handleClick={handleClick} title={'#Link-1'} />
: null
}
</Navigation>
)
Don't
export default ({changeState, myState, handleClick}) => (
<Navigation>
<h1>Navigation</h1>
<button onClick={() => changeState()}>Navigation</button>
{ myState ?
<NavigationItem handleClick={handleClick} title={'#Link-1'} />
<NavigationItem handleClick={handleClick} title={'#Link-2'} />
<NavigationItem handleClick={handleClick} title={'#Link-3'} />
: null
}
</Navigation>
)
Directly we can't return more than one elements.
Possible Solutions:
1- Either you need to wrap all the elements in a div or any other wrapper element.
2- We can return an array of multiple elements also, So put all the items in an array, and return the array.
Like this:
{myState ?
[
<NavigationItem handleClick={handleClick} title={'#Link-1'} />,
<NavigationItem handleClick={handleClick} title={'#Link-2'} />,
<NavigationItem handleClick={handleClick} title={'#Link-3'} />
]
: null
}
Check this example:
let b = true ? [1,2,3,4]: null;
console.log('b = ', b);
This will throw error:
let b = true? 1 2 3 4: null;
console.log('b = ', b);
You can also use <Fragment> from ReactJS: https://reactjs.org/docs/fragments.html
The problem about wrapping all the elements with a <div>, is that you are adding more elements to the DOM, and sometimes it's impossible (for example, when you are rendering a <td> or <tr> inside a <table>. So, here is where <Fragment> comes to help us.
Just wrap all those elements in a <Fragment> and it'll be enough.
Meaning:
{ myState &&
<Fragment>
<NavigationItem handleClick={handleClick} title={'#Link-1'} />
<NavigationItem handleClick={handleClick} title={'#Link-2'} />
<NavigationItem handleClick={handleClick} title={'#Link-3'} />
</Fragment>
}
Anyways, this another "Conditional Rendering" approach is better in "code readability" sense: https://medium.com/#BrodaNoel/conditional-rendering-in-react-and-jsx-the-solution-7c80beba1e36
It basically proposes the use of a <Conditional> element, like:
<Conditional if={myState}>
<NavigationItem handleClick={handleClick} title={'#Link-1'} />,
<NavigationItem handleClick={handleClick} title={'#Link-2'} />,
<NavigationItem handleClick={handleClick} title={'#Link-3'} />
</Conditional>
^ This looks better for my eyes :D

Categories