Wrapping functional JSX components with other functions - javascript

I want to create a functional component that wraps other components and uses JSX in its render method. I've seen a lot of content online about how to do this with class components, but I'm curious if it works with functional components.
function WrappedContent() {
return <p>I'm wrapped</p>
}
function Wrapper(WrappedComponent) {
return (
<div>
<p>Wrapped: </p>
<WrappedComponent />
</div>
)
};
function App() {
return (
<div>
<Wrapper>
<WrappedContent />
</Wrapper>
</div>
)
}
I'm guessing it has something to do with how child components are passed into <Wrapper> (via props.children?).
Here's a codesandbox with the above code: https://codesandbox.io/embed/gifted-cray-bqswi

(via props.children?)
Yes:
function WrappedContent() {
return <p>I'm wrapped</p>
}
function Wrapper(props) {
return (
<div>
<p>Wrapped: </p>
{props.children}
</div>
)
}
function App() {
return (
<div>
<Wrapper>
<WrappedContent />
</Wrapper>
</div>
)
}
ReactDOM.render(<App/>, document.getElementById("root"));
<div id="root"></div>
<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>
Note that Wrapper accepts a parameter called props, and uses props.children. You could do it with destructuring if you don't have any other props (or even if you do, but it's a small number):
function Wrapper({children}) {
return (
<div>
<p>Wrapped: </p>
{children}
</div>
)
}

What gets passed to functional components is the props, and child elements are contained in props.children:
function WrappedContent() {
return <p>I'm wrapped</p>
}
function Wrapper(props) { // <-- here
return (
<div>
<p>Wrapped: </p>
{props.children} // <-- here
</div>
)
};
function App() {
return (
<div>
<Wrapper>
<WrappedContent />
</Wrapper>
</div>
)
}
https://codesandbox.io/embed/priceless-borg-brlbk

You can try something like this.
function WrappedContent() {
return <p>I'm wrapped</p>
}
function Wrapper(props) {
return (
<div>
<p>Wrapped: </p>
{props.children}
</div>
)
};
function App() {
return (
<div>
<Wrapper>
<WrappedContent />
</Wrapper>
</div>
)
}
You might also want to refer at this article https://reactjs.org/docs/composition-vs-inheritance.html

Related

Why I can't call useRef inside callback?

When I write this code I have an error:
React Hook "useRef" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function
What should I do with this code?
return ITEMS.map((item, i) => {
const elementRef = useRef(null);
return (
<div
ref={elementRef}
key={i}
>
<p>{item.name}</p>
<Wrapper>
{item.name === visibleItem && (
<Item
parentRef={elementRef}
/>
)}
</Wrapper>
</div>
);
}
Here are two possibilities, Either using useRef with an object/array, or using createRef as suggested by Yevgen Gorbunkov.
I'm not entirely sure as to the viability of these as the createRef option will create entirely new refs on each render, and the useRef option you'll need to make sure your keys/indexes are always the same.
const ITEMS = [{ name: "test" }, { name: "test2" }];
export default function App() {
const ref = useRef({});
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{ITEMS.map((item, idx) => {
return (
<div key={idx} ref={element => (ref.current[idx] = element)}>
<p>{item.name}</p>
<Wrapper>
{item.name === visibleItem && (
<Item parentRef={ref.current[idx]} />
)}
</Wrapper>
</div>
);
})}
{ITEMS.map((item, idx) => {
const ref = createRef();
return (
<div key={idx} ref={ref}>
<p>{item.name}</p>
<Wrapper>
{item.name === visibleItem && <Item parentRef={ref} />}
</Wrapper>
</div>
);
})}
</div>
);
}

React how to pass a component with its props to another component

I need to pass a wrapper to the component Test. This wrapper could be anything and it needs to have its own props (function, bool, string, whatever).
function App() {
return (
<div className="App">
<h1>Yo</h1>
<Test Wrapper={<CustomWrapper text={"yes"} />} />
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
The component Test receives it as a prop and renders it including the children (here 'Nope')
function Test({ Wrapper }) {
return (
<div>
<Wrapper>
<div>Nope</div>
</Wrapper>
</div>
);
}
What is the right pattern to do that?
Pass the child as a prop would work
import React from "react";
import "./styles.css";
export default function App() {
return (
<div className="App">
<h1>Yo</h1>
<Test Wrapper={CustomWrapper({ text: "yes" })} />
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const Test = ({ Wrapper }) => {
return (
<div>
<Wrapper children={<div>Nope</div>} />
</div>
);
};
const CustomWrapper = props => ({ children }) => {
return (
<div>
{children}
{props.text}
</div>
);
};

Cleaner react code when using loops and map

I have this code working with react, and its just getting very cluttered, so I was wondering if there is a way to make this code and others that are quite similar to look cleaner.
render() {
let result = null;
var obj = this.state.welcome;
let test = null;
if (this.state.isReal) {
test = Object.entries(obj).map(([key, value], index) => {
return (
<li key={index}>
Word: "{key}" repeats: {value} times
</li>
);
});
result = (
<Aux>
<h3>Title</h3>
<ul>{test}</ul>
</Aux>
);
}
return (
<Aux>
<div className="bframe">
<div className="form" />
{result}
</div>
<Footer />
</Aux>
);
}
I was wondering if its possible to move everything before 'return' statement, preferable in a separate file. I tried making a functional component and passing props but im unable to do loops there. Any tips?
You can reduce your code to the following :
render() {
const { welcome, isReal } = this.state
return (
<Aux>
<div className="bframe">
<div className="form" />
{isReal &&
<Aux>
<h3>Title</h3>
<ul>
{Object.entries(welcome).map(([key, value]) =>
<li key={key}>
Word: "{key}" repeats: {value} times
</li>
)}
</ul>
</Aux>
}
</div>
<Footer />
</Aux>
);
}
Do not use var, by default use const and if you want to modify your variable, use let.
You can choose to render an element or not by using the inline if : &&.
Your function is also unnecessary as it can be replaced by inline JS.
Your map can also be reduce from : x.map(a => { return <div/> } to x.map(a => <div/>.
You can also use the key of each item as the React key since they all have to be unique anyway in your object.
Maybe something like the following
const Result = ({real, welcome}) => {
if (!real) return null;
const words = Object.entries(welcome).map(([key, value], index) => <li key={index}>
Word: "{key}" repeats: {value} times
</li>
);
return (
<Aux>
<h3>Title</h3>
<ul>{words}</ul>
</Aux>
);
}
class YourComponent extends React.Component {
// ...
render() {
const {isReal, welcome} = this.state;
return (
<Aux>
<div className="bframe">
<div className="form" />
<Result real={isReal} welcome={welcome}/>
</div>
<Footer />
</Aux>
);
}
}

onClick listener not firing in react

I'm trying to make a simple todo app in react and am at the point where I am trying to delete items. So I have created a removeTodo method in my App.js file.
I have a todoList component which loops through the todosarray in the state.todos and then injects the todocomponent. Although I am looking at a way in which I can pass the removeTodo function down to the todo component. Here is my attempt at doing this...
removeTodo(){
//just need this function to fire
console.log("1234")
}
render() {
return (
<div className="App">
<div className="header">
<h1>Todo Application</h1>
</div>
<div className="header">
<input
type="text"
ref={((ref) => this.input = ref)}
value={this.state.todoText}
onKeyPress={this.handleSub.bind(this)}
/>
</div>
<TodoList todos={this.state.todos} remove={this.removeTodo.bind(this)}/>
//passing in removeTodo method to props
</div>
);
}
Here's my todoList Component
function todoList(props){
return (
<ul className="todoList">
{props.todos.map((todo, index) => {
return(
<Todo onClick={props.remove} name={todo.name} key={index}/>
//the todo component just renders an li with the name inside the todos
array
);
})}
</ul>
);
}
Whenever I go to click on the rendered Todo nothing happens, why is the onClick not firing? I'm new to react so sorry in advance for any ignorance
onClick will be like any other prop given to the Todo component, so you need to add the onClick function in the props to a onClick prop on an element in Todo as well.
Example
function TodoList(props) {
return (
<ul className="todoList">
{props.todos.map((todo, index) => (
<Todo
onClick={() => console.log(`Clicked ${index}!`)}
name={todo.name}
key={index}
/>
))}
</ul>
);
}
function Todo(props) {
return <li onClick={props.onClick}>{props.name}</li>;
}
ReactDOM.render(
<TodoList todos={[{ name: "foo" }, { name: "bar" }]} />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

ReactJS remove root component on certain page

I am looking for a solution that will remove a <header/> and <footer/> component from my signup.js and signin.js pages.
Currently, my root index.js file is shown as
class Template extends React.Component {
render() {
const { children } = this.props
return (
<main>
<Header/>
{children()}
<Footer/>
</main>
)
}}
Template.propTypes = {
children: PropTypes.func
}
export default Template
Which is the main layout for all my page, posts, products, etc. Yet without creating another layout, I would like to conditionally remove the <header/> and <footer/> components from being a part of pages signup.js and signin.js
As suggested by GatsbyJS I have tried - of which is removing the components from all pages.
if (this.props.location.pathname !== "/signup/") {
return (
<main>
{children()}
</main>
)
} else {
return this (
<main>
<Header/>
{children()}
<Footer/>
</main>
)
}
I would use a different template for your signin and signup components, but if you don't do that:
You have a typo in your code, in your else you are returning this(...) it should return (...). This way:
if (this.props.location.pathname !== "/signup/") {
return (
<main>
{children()}
</main>
)
} else {
return (
<main>
<Header/>
{children()}
<Footer/>
</main>
)
}
Also, perhaps your if condition is inverted... because in /signup/ you don't want Header and Footer:
if (this.props.location.pathname === "/signup/" || this.props.location.pathname === "/signin/") {
return (
<main>
{children()}
</main>
)
} else {
return (
<main>
<Header/>
{children()}
<Footer/>
</main>
)
}
Alternatively, if you don't want to duplicate code...
const isSignIn = ["/signup/", "/signin/"].indexOf( this.props.location.pathname) !== 0;
return (
<main>
{ !isSignIn && (<Header/>) }
{children()}
{ !isSignIn && (<Footer/>) }
</main>
)

Categories

Resources