I'm somewhat confused on the relationship between functional components in React and the Render Props and HOC patterns.
That is,
is it true that the only way to create Render Prop is with a class component?
is it true that the only way to create an HOC is with a class component?
And the same for usage.
I'm trying to find examples of Render Props and HOC's with functional components and all I find are class components. I get that React Hooks do a lot of the same, but I'm trying to understand how the Render Props and HOC patterns can apply to functional components (or if they do at all)
Edit Below:
Applying what #chaimFriedman suggested, this is what I came up with using no class or component for an HOC hoping it makes sense.
import React, { useState, useEffect } from 'react';
import useAxios from 'axios-hooks';
function withFetching(url) {
return function(Speakers) {
return () => {
const [speakerData, setSpeakerData] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [{ data, loading }, refetch] = useAxios('http://localhost:4000/speakers');
useEffect(() => {
setSpeakerData(data);
setIsLoading(loading);
}, [loading]);
if (isLoading) return <div>loading..</div>;
return <Speakers data={speakerData}></Speakers>;
};
};
}
const Speakers = function(props) {
//debugger;
return (
<ul>
{props.data.map((speaker) => (
<li key={speaker.id}>
<span>
{speaker.firstName} {speaker.lastName}
</span>
</li>
))}
</ul>
);
};
const API = 'http://localhost:4000/speakers';
export default withFetching(API)(Speakers);
Both render props and HOC can absolutely apply to functional components. Let's think a little more about what each one really is to see why they do in fact work with functions as well as classes.
Render props is when you have a prop that is a function which returns JSX. This of course should work for function components because aside from life cycle methods there really isnt much that is different than class components. Here is an example with code.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Renderer(props) {
return (
props.children()
);
}
function App() {
return (
<div className="App">
<Renderer>
{() => {
return (
<h1>I am being rendered by Renderer</h1>
);
}}
</Renderer>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Now for HOC.
A HOC really is just a higher order function, but because we use it in react we call it a higher order component. A higher order function is a function which either accepts a function as an argument, or returns a function. Now a functional component can absolutely do this. Here is an example.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function Renderer(Wrapped) {
return function New(props) {
return <Wrapped {...props} />
}
}
function Child(props) {
return (
<h1>Hello {props.name}</h1>
);
}
function App() {
const C = Renderer(Child)
return (
<div className="App">
<C name="john" />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
EDIT: I realized my HOC example was wrong so updated.
I hope this helps.
Here is the sample code converting example from React documentation into function component. https://reactjs.org/docs/render-props.html
import React from "react";
const Cat = ({mouse}) => {
return (
<img
src="/cat.png"
alt="cat"
style={{ position: "absolute", left: mouse.x, top: mouse.y }}
/>
);
};
const Mouse = (props) => {
const [state, setState] = React.useState();
const handleMouseMove = (event) => {
setState({
x: event.clientX,
y: event.clientY
});
};
return (
<div style={{ height: "100vh" }} onMouseMove={handleMouseMove}>
{props.render(state)}
</div>
);
};
const MouseTracker = () => {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={(mouse) => <Cat mouse={mouse} />} />
</div>
);
};
export const App = () => {
return (
<div className="App">
<MouseTracker />
</div>
);
}
Related
I am generating my state in the parent component. latestFeed generates a series of posts from my backend:
import React, { useState, useEffect } from "react";
import { getLatestFeed } from "../services/axios";
import Childfrom "./Child";
const Parent= () => {
const [latestFeed, setLatestFeed] = useState("loading");
const [showComment, setShowComment] = useState(false);
useEffect(async () => {
const newLatestFeed = await getLatestFeed(page);
setLatestFeed(newLatestFeed);
}, []);
return (
<div className="dashboardWrapper">
<Child posts={latestFeed} showComment={showComment} handleComment={handleComment} />
</div>
);
};
export default Parent;
then latestFeed gets generated into a series of components that all need to hold their own state.
import React, { useState } from "react";
const RenderText = (post, showComment, handleComment) => {
return (
<div key={post._id} className="postWrapper">
<p>{post.title}</p>
<p>{post.body}</p>
<Comments id={post._id} showComment={showComment} handleComment={() => handleComment(post)} />
</div>
);
};
const Child= ({ posts, showComment, handleComment }) => {
return (
<div>
{posts.map((post) => {
return RenderText(post, showComment, handleComment);
})}
</div>
);
};
export default Child;
In its current form, the state of RenderText's is all set at the same time. I need each child of Child to hold its own state.
Thank you!
Instead of using RenderText as a function, call it as a component:
{posts.map((post) => (
<RenderText key={post.id} post={post} showComment={showComment} />
))}
This is because when used as a component, it will have it's own lifecycle and state. If used as a function call, React does not instantiate it the same way - no lifecycle, no state, no hooks, etc.
I'm trying to get hold of ref on children component but it doesn't seem to be working. The same approach works fine with the React class component but not with hooks.
import React, { useState, useRef } from "react";
export default function TestContainer(props) {
const ref = useRef(null);
return (
<div className="test-container" onClick={() => console.log(ref) // this logs null always}>
{React.Children.map(props.children, c =>
React.cloneElement(c, {
ref: n => {
console.log(n);
ref.current = n;
},
className: "test-container"
})
)}
</div>
);
}
export function Test(props) {
return <div className="test" {...props}>
{props.children}
</div>
}
Your component is okay. It is probably because the are no children rendered to that component. I reproduced it with using TestContainer in App and put <h2>Ref</h2> as a child of TestContainer:
(removed the comment of course, since it has been hiding the } )
App.js
import React from "react";
import "./styles.css";
import TestContainer from "./TestContainer";
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<TestContainer>
<h2>Ref</h2>
</TestContainer>
</div>
);
}
TestContainer.js
import React, { useState, useRef } from "react";
export default function TestContainer(props) {
const ref = useRef(null);
return (
<div className="test-container" onClick={() => console.log(ref)}>
{React.Children.map(props.children, c =>
React.cloneElement(c, {
ref: n => {
console.log(n);
ref.current = n;
},
className: "test-container"
})
)}
</div>
);
}
CodeSndbox:
HERE
I'm trying to use React Context to update navbar title dynamically from other child components. I created NavbarContext.js as follows. I have wrapped AdminLayout with NavContext.Provider and use useContext in Course.js to dynamically update navbar title inside useEffect. However, when I'm doing this, react throws the following error on the screen.
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
How can I use context properly so that I can update Header title from Course.js inside its useEffect?
NavbarContext.js
import React, {useState} from 'react'
export default () => {
const [name,setName] = useState("")
const NavContext = React.createContext({
name: "",
changeName: name => setName(name)
})
const NavProvider = NavContext.Provider
const NavConsumer = NavContext.Consumer
return NavContext
}
AdminLayout.js
<NavContext.Provider>
<div className={classes.wrapper}>
<Sidebar
routes={routes}
logoText={"Widubima"}
logo={logo}
image={image}
handleDrawerToggle={handleDrawerToggle}
open={mobileOpen}
color={color}
{...rest}
/>
<div className={classes.mainPanel} ref={mainPanel}>
<Navbar
routes={routes}
handleDrawerToggle={handleDrawerToggle}
{...rest}
/>
{/* On the /maps route we want the map to be on full screen - this is not possible if the content and conatiner classes are present because they have some paddings which would make the map smaller */}
{getRoute() ? (
<div className={classes.content}>
<div className={classes.container}>{switchRoutes}</div>
</div>
) : (
<div className={classes.map}>{switchRoutes}</div>
)}
</div>
</div>
</NavContext.Provider>
Navbar.js
import NavContext from "context/NavbarContext"
export default function Header(props) {
function makeBrand() {
var name;
props.routes.map(prop => {
if (window.location.href.indexOf(prop.layout + prop.path) !== -1) {
name = prop.name;
document.title = name;
}
return null;
});
return name;
}
return (
<AppBar className={classes.appBar + appBarClasses}>
<Toolbar className={classes.container}>
<div className={classes.flex}>
{/* Here we create navbar brand, based on route name */}
<NavContext.Consumer>
{({ name, setName }) => (
<Button
color="transparent"
href="#"
className={classes.title}
style={{ fontSize: "1.5em", marginLeft: "-2%" }}
>
{makeBrand() || name}
</Button>
)}
</NavContext.Consumer>
</Toolbar>
</AppBar>
);
}
Course.js
import React, { useState, useEffect, useContext } from "react";
import NavContext from "context/NavbarContext"
const AdminCourse = props => {
const context = useContext(NavContext);
useEffect(() => {
Axios.get('/courses/'+props.match.params.courseId).then(
res => {
context.changeName("hello")
}
).catch(err => {
console.log(err)
})
return () => {
setCourseId("");
};
});
return (
<GridContainer>
</GridContainer>
);
};
export default AdminCourse;
i think problem is there with your NavbarContext.js.
you are not exporting NavContext also.
you are defining provider, consumer but you are not using them either.
here's how you can solve your problem.
first create context and it's provider in a file as following.
NavContext.js
import React, { useState } from "react";
const NavContext = React.createContext();
const NavProvider = props => {
const [name, setName] = useState("");
let hookObject = {
name: name,
changeName: setName
};
return (
<NavContext.Provider value={hookObject}>
{props.children}
</NavContext.Provider>
);
};
export { NavProvider, NavContext };
in above code first i am creating context with empty value.
the i am creating NavProvider which actually contains value name as a state hook inside it.hookObject exposes state as per your naming conventions in code.
now i for testing purpose i defined two consumers.
one is where we update name in useEffect, that is ,
ConsumerThatUpdates.js
import React, { useContext, useEffect } from "react";
import { NavContext } from "./NavContext";
const ConsumerThatUpdates = () => {
const { changeName } = useContext(NavContext);
useEffect(() => {
changeName("NEW NAME");
}, [changeName]);
return <div>i update on my useeffect</div>;
};
export default ConsumerThatUpdates;
you can update useEffect as per your needs.
another is where we use the name,
ConsumerThatDisplays.js
import React, { useContext } from "react";
import { NavContext } from "./NavContext";
const ConsumerThatDisplays = () => {
const { name } = useContext(NavContext);
return <div>{name}</div>;
};
export default ConsumerThatDisplays;
and finally my App.js looks like this,
App.js
import React from "react";
import "./styles.css";
import { NavProvider } from "./NavContext";
import ConsumerThatDisplays from "./ConsumerThatDisplays";
import ConsumerThatUpdates from "./ConsumerThatUpdates";
export default function App() {
return (
<div className="App">
<NavProvider>
<ConsumerThatDisplays />
<ConsumerThatUpdates />
</NavProvider>
</div>
);
}
hope this helps!!
if you want to know more about how to use context effectively, i recooHow to use React Context effectively
I have the following code where I am trying to get a value from one component to its sibling using the context api.
import React from "react";
import ReactDOM from "react-dom";
function App() {
return (
<div>
<TheButton />
<Display />
</div>
);
}
export const NumberContext = React.createContext();
function TheButton() {
return (
<NumberContext.Provider value={"test"}>
<button>Click me</button>
</NumberContext.Provider>
);
}
function Display() {
const context = React.useContext(NumberContext);
return <div>The answer {context}.</div>;
}
ReactDOM.render(<App />, document.querySelector("#root"));
As you can see I am passing the 'test' value in the provider, but when the page renders all I see is "The answer ."
Here is a a codesandbox for the issue https://codesandbox.io/s/pedantic-forest-zjlc2
Despite the fact that context gives you a decoupled way of passing props the Provider still must be on a higher hierarchy. Provide your context from your App and consume it from children.
export const NumberContext = React.createContext();
function App() {
const [foo, changeFoo] = useState('foo')
return (
<NumberContext.Provider value={{ foo, changeFoo }}>
<TheButton />
<Display />
</NumberContext.Provider>
);
}
function TheButton() {
const { changeFoo } = useContext(NumberContext)
return (
<button onClick={() => changeFoo('bar')}>Click me</button>
);
}
function Display() {
const context = React.useContext(NumberContext);
return <div>The answer {context.foo}.</div>;
}
Would it be considered bad practice, using contextProvider without a consumer. Like I did below. I found out it worked just fine. Not exactly sure if it's the right way.
// context
import React, { useState } from 'react'
import { storeProducts, detailProduct } from './assets/data'
const ProductContext = React.createContext()
const ProductProvider = (props) => {
const [products, setProducts] = useState(storeProducts);
const [productDetails, setProductDetails] = useState(detailProduct)
console.log(products)
return (
<ProductContext.Provider value={{
products,
productDetails
}}>
{props.children}
</ProductContext.Provider>
)
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer, ProductContext }
/// Context usage
import { ProductContext } from './context'
export default function ProductList() {
const appContext = useContext(ProductContext)
const { products } = appContext
console.log(appContext)
return (
<div className="py-5">
<div className="container">
<Title name="Product" title="Store"></Title>
<div className="row">
{/* <ProductConsumer>
{(product) => <{product}}
</ProductConsumer> */}
{products.map(p => {
return <h1>{p.title}</h1>
})}
</div>
</div>
</div>
It works just fine, just wanted to know it this could have side effects problems down the line.Or if it is outright discouraged and why.
Unlike class components, in functional components there is a different way to make use of context value with useContext hook and is an alternative to writing a Consumer and then using the render prop pattern.
export default function ProductList() {
const appContext = useContext(ProductContext)
const { products } = appContext
console.log(appContext)
return (
<div className="py-5">
<div className="container">
<Title name="Product" title="Store"></Title>
<div className="row">
{products.map(p => {
return <h1>{p.title}</h1>
})}
</div>
</div>
</div>
)
}
So, not defining a consumer doesn't mean you don't have a consumer of the context value
Doesn't seem like something bad you are doing -
Following is the useContext way of consuming the values -
import React, { useContext } from 'react';
// ...
function Display() {
const value = useContext(NumberContext);
return <div>The answer is {value}.</div>;
}
which seems like the way you are doing it.
"The only thing you want to watch out for is that you have to pass the whole context object to useContext – not just the consumer! React will warn you if you forget, but try to rememeber, eh?" - is the only difference between useContext hook and Comsumer.
For more details you can read about it -
https://daveceddia.com/usecontext-hook/