Is having a lot of render methods within a components bad practice? - reactjs

I had written a component and there are some other instances of small components within that components. Is it a bad practice to use a renderxxx for each instance? thank you

It depends. If you are using some kind of logic for rendering your components, then you should go with dedicated renderX methods. But if all you are doing is to return the components, you should simply describe it with JSX.
Without logic
const MyComponent = ({ options }) => (
<div>
<SelectList options={options} />
</div>
);
With logic
const MyComponent = ({ options }) => {
const renderOptions = () => {
if (options.length < 5) {
return <RadioButtonGroup options={options} />;
}
return <SelectList options={options} />;
};
return (
<div>
{renderOptions()}
</div>
);
};
Another approch would be wrapping another component around every renderX method.
const OptionsRenderer = ({ options }) => {
if (options.length < 5) {
return <RadioButtonGroup options={options} />;
}
return <SelectList options={options} />;
};
const MyComponent = ({ options }) => (
<div>
<OptionsRenderer options={options} />
</div>
);

Related

My event onClick in my map does not work. Very strange behavior

My Onclick on bestmovies map does not work. If I place it on a H1, for example, works. onClick={handleClickMovie}
// imports....
const Movies = () => {
const [popularMovies, setPopularMovies] = useState([])
const [bestMovies, setBestMovies] = useState([])
const [showPopUp, setShowPopUp] = useState(false)
const handleClickMovie = () => {
setShowPopUp(console.log('Clicked'))
}
useEffect(() => {
async function getMovies() {
const responsePopularMovies = await getPopularMovies()
setPopularMovies(responsePopularMovies.results)
const responseBestMovies = await getBestMovies()
setBestMovies(responseBestMovies.results)
}
getMovies()
}, [])
return (
<div>
<Wrapper>
{showPopUp ? <MoviePopUp /> : null}
<h1>Filmes Populares</h1>
<Content>
{popularMovies.map(item => (
<MovieItem movie={item} />
))}
</Content>
<h1>Filmes Bem Avaliados</h1>
<Content>
{bestMovies.map(item => (
<MovieItem movie={item} onClick={handleClickMovie} />
))}
</Content>
</Wrapper>
</div>
)
}
export default Movies
MovieItem.js
import React from 'react'
import { Cover, Score, Title } from './MovieItem.styles'
const MovieItems = ({ movie }) => {
return (
<Cover key={movie.id}>
<img
src={`https://image.tmdb.org/t/p/original${movie.poster_path}`}
alt="capas"
/>
<Score>{movie.vote_average}</Score>
<Title>{movie.title}</Title>
</Cover>
)
}
export default MovieItems
try wrapping in a div
<Content>
{bestMovies.map(item => (
<div onClick={handleClickMovie}>
<MovieItem movie={item} onClick={handleClickMovie} />
</div>
))}
</Content>
As #anthony_718 answered, you are calling onClick on a JSX component. JSX components aren't in the DOM and don't have click events (although they can render HTML elements if they contain them).
If you want, you can also pass the props all the way up to an actual html element the <Cover> renders.
#anthony_718's answer is correct.
The reason it didn't work it's because <MovieItem> doesn't have onClick in his props.
However, to facilitate reusability, you can modify your component like so:
const MovieItems = ({ movie, onClick }) => {
return (
<div onClick={onClick}>
<Cover key={movie.id}>
// ... rest of your stuff
</Cover>
</div>
)
}
export default MovieItems
It's essentially the same solution, but by placing <div onClick> within the component definition, you make it more reusable than the other option.
check this
bestMovies.map((item, i) => { return( <MovieItem movie={item} onClick={handleClickMovie} /> )})

Pass setState as props or create a handle

Can I pass the setPlaying as props? Or I have to do something like this example code?
Components Two and Three can be in their own file.
export const myComponent = () => {
const [playing, setPlaying] = useState(false);
const handleChange = (show) => {
setPlaying(show);
};
return (
<>
<One />
{!playing ? (
<Two handleChange={handleChange} />
) : (
<Three handleChange={handleChange} />
)}
</>
)
};
const Two = ({ handleChange }) => {
return (
<Container>
<Button onClick={{(e) => handleChange(true)}}>Click to Show Component Three</Button>
</Container>
);
};
const Three = ({ handleChange }) => {
return (
<Container>
<Button onClick={{(e) => handleChange(false)}}>Click to Show Component Two</Button>
</Container>
);
};
Of course. Wrapping setPlaying with handleChange is advantageous if you wish to do any further processing in myComponent. And the conditional rendering will prevent needless re-renders.
Yes, you can do that. Also, it is a good practice that you did with the handler.
ref: Passing setState to child component using React hooks

Update state from deeply nested component without re-rendering parents

I have a form page structured more or less as follows:
<Layout>
<Page>
<Content>
<Input />
<Map />
</Content>
</Page>
<Button />
</Layout>
The Map component should only be rendered once, as there is an animation that is triggered on render. That means that Content, Page and Layout should not re-render at all.
The Button inside Layout should be disabled when the Input is empty. The value of the Input is not controlled by Content, as a state change would cause a re-render of the Map.
I've tried a few different things (using refs, useImperativeHandle, etc) but none of the solutions feel very clean to me. What's the best way to go about connecting the state of the Input to the state of the Button, without changing the state of Layout, Page or Content? Keep in mind that this is a fairly small project and the codebase uses "modern" React practices (e.g. hooks), and doesn't have global state management like Redux, MobX, etc.
Here is an example (click here to play with it) that avoids re-render of Map. However, it re-renders other components because I pass children around. But if map is the heaviest, that should do the trick. To avoid rendering of other components you need to get rid of children prop but that most probably means you will need redux. You can also try to use context but I never worked with it so idk how it would affect rendering in general
import React, { useState, useRef, memo } from "react";
import "./styles.css";
const GenericComponent = memo(
({ name = "GenericComponent", className, children }) => {
const counter = useRef(0);
counter.current += 1;
return (
<div className={"GenericComponent " + className}>
<div className="Counter">
{name} rendered {counter.current} times
</div>
{children}
</div>
);
}
);
const Layout = memo(({ children }) => {
return (
<GenericComponent name="Layout" className="Layout">
{children}
</GenericComponent>
);
});
const Page = memo(({ children }) => {
return (
<GenericComponent name="Page" className="Page">
{children}
</GenericComponent>
);
});
const Content = memo(({ children }) => {
return (
<GenericComponent name="Content" className="Content">
{children}
</GenericComponent>
);
});
const Map = memo(({ children }) => {
return (
<GenericComponent name="Map" className="Map">
{children}
</GenericComponent>
);
});
const Input = ({ value, setValue }) => {
const onChange = ({ target: { value } }) => {
setValue(value);
};
return (
<input
type="text"
value={typeof value === "string" ? value : ""}
onChange={onChange}
/>
);
};
const Button = ({ disabled = false }) => {
return (
<button type="button" disabled={disabled}>
Button
</button>
);
};
export default function App() {
const [value, setValue] = useState("");
return (
<div className="App">
<h1>SO Q#60060672</h1>
<Layout>
<Page>
<Content>
<Input value={value} setValue={setValue} />
<Map />
</Content>
</Page>
<Button disabled={value === ""} />
</Layout>
</div>
);
}
Update
Below is version with context that does not re-render components except input and button:
import React, { useState, useRef, memo, useContext } from "react";
import "./styles.css";
const ValueContext = React.createContext({
value: "",
setValue: () => {}
});
const Layout = memo(() => {
const counter = useRef(0);
counter.current += 1;
return (
<div className="GenericComponent">
<div className="Counter">Layout rendered {counter.current} times</div>
<Page />
<Button />
</div>
);
});
const Page = memo(() => {
const counter = useRef(0);
counter.current += 1;
return (
<div className="GenericComponent">
<div className="Counter">Page rendered {counter.current} times</div>
<Content />
</div>
);
});
const Content = memo(() => {
const counter = useRef(0);
counter.current += 1;
return (
<div className="GenericComponent">
<div className="Counter">Content rendered {counter.current} times</div>
<Input />
<Map />
</div>
);
});
const Map = memo(() => {
const counter = useRef(0);
counter.current += 1;
return (
<div className="GenericComponent">
<div className="Counter">Map rendered {counter.current} times</div>
</div>
);
});
const Input = () => {
const { value, setValue } = useContext(ValueContext);
const onChange = ({ target: { value } }) => {
setValue(value);
};
return (
<input
type="text"
value={typeof value === "string" ? value : ""}
onChange={onChange}
/>
);
};
const Button = () => {
const { value } = useContext(ValueContext);
return (
<button type="button" disabled={value === ""}>
Button
</button>
);
};
export default function App() {
const [value, setValue] = useState("");
return (
<div className="App">
<h1>SO Q#60060672, method 2</h1>
<p>
Type something into input below to see how rendering counters{" "}
<s>update</s> stay the same
</p>
<ValueContext.Provider value={{ value, setValue }}>
<Layout />
</ValueContext.Provider>
</div>
);
}
Solutions rely on using memo to avoid rendering when parent re-renders and minimizing amount of properties passed to components. Ref's are used only for render counters
I have a sure way to solve it, but a little more complicated.
Use createContext and useContext to transfer data from layout to input. This way you can use a global state without using Redux. (redux also uses context by the way to distribute its data). Using context you can prevent property change in all the component between Layout and Imput.
I have a second easier option, but I'm not sure it works in this case. You can wrap Map to React.memo to prevent render if its property is not changed. It's quick to try and it may work.
UPDATE
I tried out React.memo on Map component. I modified Gennady's example. And it works just fine without context. You just pass the value and setValue to all component down the chain. You can pass all property easy like: <Content {...props} /> This is the easiest solution.
import React, { useState, useRef, memo } from "react";
import "./styles.css";
const Layout = props => {
const counter = useRef(0);
counter.current += 1;
return (
<div className="GenericComponent">
<div className="Counter">Layout rendered {counter.current} times</div>
<Page {...props} />
<Button {...props} />
</div>
);
};
const Page = props => {
const counter = useRef(0);
counter.current += 1;
return (
<div className="GenericComponent">
<div className="Counter">Page rendered {counter.current} times</div>
<Content {...props} />
</div>
);
};
const Content = props => {
const counter = useRef(0);
counter.current += 1;
return (
<div className="GenericComponent">
<div className="Counter">Content rendered {counter.current} times</div>
<Input {...props} />
<Map />
</div>
);
};
const Map = memo(() => {
const counter = useRef(0);
counter.current += 1;
return (
<div className="GenericComponent">
<div className="Counter">Map rendered {counter.current} times</div>
</div>
);
});
const Input = ({ value, setValue }) => {
const counter = useRef(0);
counter.current += 1;
const onChange = ({ target: { value } }) => {
setValue(value);
};
return (
<>
Input rendedred {counter.current} times{" "}
<input
type="text"
value={typeof value === "string" ? value : ""}
onChange={onChange}
/>
</>
);
};
const Button = ({ value }) => {
const counter = useRef(0);
counter.current += 1;
return (
<button type="button" disabled={value === ""}>
Button (rendered {counter.current} times)
</button>
);
};
export default function App() {
const [value, setValue] = useState("");
return (
<div className="App">
<h1>SO Q#60060672, method 2</h1>
<p>
Type something into input below to see how rendering counters{" "}
<s>update</s> stay the same, except for input and button
</p>
<Layout value={value} setValue={setValue} />
</div>
);
}
https://codesandbox.io/s/weathered-wind-wif8b

React useState hook - component is rendered twice

I wonder why my component SearchResults is rendered twice.
In MainPage component I want to pass offers to child component SearchResults:
const mainPage = () => {
const [offers, setOffers] = useState(null);
useEffect(() => {
onInitOffers();
}, [])
const onInitOffers = () => {
axios.get('/offers')
.then(response => {
setOffers(response.data);
})
.catch(error => {
console.log(error);
})
}
const searchResults = (
<SearchResults
searchedOffers={offers}
/>
);
return (
<Aux>
<div className={classes.container}>
<div className={classes.contentSection}>
{searchResults}
</div>
</div>
</Aux>
)
}
export default mainPage;
Why the component SearchResults is rendered twice? How to correctly pass offers to child component using hooks?
In my child component SearchResults I have to add if condition to avoid error map is not a function:
const searchResults = props => {
useEffect(() => {
console.log("RENDER");
console.log(props.searchedOffers) --> null for the first time
}, [props.searchedOffers]);
let offers = null;
if (props.searchedOffers !== null) { --> props.searchedOffers is not null after the second render
offers = props.searchedOffers.map(offer => {
return (
<Grid key={offer.id}>
<SearchResult key={offer.id} offer={offer}/>
</Grid>
)
});
}
It's rendered twice because, when the element mounts, you set offers to null. If you want to make sure you only render the SearchResults component when offers isn't null, you can do something like:
return (
<Aux>
<div className={classes.container}>
<div className={classes.contentSection}>
{offers && <SearchResult searchedOffers={offers} />}
</div>
</div>
</Aux>
)
If you want to be super sure offers is an array, you can do something like {Array.isArray(offers) && <SearchResult searchedOffers={offers} />}.
Often when doing something async like this, you might elect to actually use a ternary operator to show a loading indicator while the fetch is happening:
return (
<Aux>
<div className={classes.container}>
<div className={classes.contentSection}>
{offers ? <SearchResult searchedOffers={offers} /> : "Loading..."}
</div>
</div>
</Aux>
)

Can React selectors return jsx?

When I write my React applications I keep all state, props calculations in separate functions. I call them selectors, though I'm not sure if those are selectors. By definition, selectors are functions which return subsets of state or props. I have several questions concerning this issue:
1) Can selectors return jsx objects?
For example, I would like to populate <Item /> into another component
selectorItem = (state, props) => {
const { items } = props;
const ItemsJSX = items.map((item) => {
return (
<Item
item={item}
key={item.id}
/>
)
});
return {
items: ItemsJSX
}
}
class Page extends Component {
render() {
return (
<List
{...selectorItem(this.state, this.props)}
/>
)
}
}
Is this a valid selector?
If not, how can I populate JSX into another component using selectors?
2) Should I write selector per component (selector returns object with multiple props) or per prop (separate selector per prop)?
selectorItemsComments = (state, props) => {
const { items } = props;
const { comments } = props;
const ItemsJSX = items.map((item) => {
return (
<Item
item={item}
key={item.id}
/>
)
});
const CommentsJSX = comments.map((comment) => {
<Comment
comment={comment}
key={comment.id}
/>
});
return {
items: ItemsJSX,
comment: CommentsJSX
}
}
versus
selectorItems = (state, props) => {
const { items } = props;
const ItemsJSX = items.map((item) => {
return (
<Item
item={item}
key={item.id}
/>
)
});
return {
items: ItemsJSX
}
}
selectorComments = (state, props) => {
const { comments } = props;
const CommentsJSX = comments.map((comment) => {
return (
<Comment
comment={comment}
key={comment.id}
/>
)
});
return {
comment: CommentsJSX
}
}
Thank you
These are not selectors the are just functions returning components
TL;DR
The short answer is yes you can return Components from functions.
Full Answer
In your simpler first example you should just return the component array from your selectorItem (I have renamed this to renderItems) function:
const renderItems = (items) => items.map((item) =>
<Item
item={item}
key={item.id}
/>
);
const renderComments = (comments) => comments.map((comment) =>
<Comment
item={comment}
key={comment.id}
/>
);
class Page extends Component {
render() {
const {items, comments} = this.props;
return (
<List>
{renderItems(items)}
{renderComments(comments)}
</List>
);
}
}
I would recommend converting renderItems to a Component and then you could do something like:
<List>
<Items items={items} />
<Comments comments={comments} />
</List>
If I understand correctly, your selector is still a React component. This pattern is called "Smart vs Dumb" component, or Presentational and Container Components.
Is HOCs (Higher Order Component) what you want ?

Resources