I'm using Higher Order Components to decorate my components.
const HOC = (WrappedComponent) => (props) => {
return (
<span>
<p>HOC Comp</p>
<WrappedComponent {...props}/>
</span>
)
}
I do like this pattern explained here: React Higher Order Components in depth
However I have a problem because the HOC causing React to recreate my component tree instead of updating the tree. This is nicely explained here React Reconciliation. HOC returns an anonymous function whereby React doesn't know it is actually rendering the same component. This is bad for performance and makes my input field lose focus.
How could I use HOC components without React recreating my tree on every render()?
Example code:
class Input extends React.Component {
componentWillMount() {
console.log('input component will mount');
}
componentWillUnmount() {
console.log('input component will unmount');
}
render() {
return (
<span>
<input value={this.props.value} onChange={this.props.onChange}/>
</span>
);
}
}
const HOC = (WrappedComponent) => {
const Help = (props) => {
return (
<span>
<WrappedComponent {...props}/>
<p>{props.help}</p>
</span>
)
};
return Help;
}
class MyComponent extends React.Component {
constructor (props) {
super(props);
this.state = {value : 'start value'}
}
onChange(event) {
this.setState({value : event.target.value});
}
render() {
const Element = HOC(Input);
return (
<span>
<Element
value={this.state.value}
onChange={this.onChange.bind(this)}
/>
</span>
)
}
}
ReactDOM.render(
<MyComponent />,
document.getElementById('container')
);
See Fiddle example (see in your browser's console to see the mount and unmount logs from the input component every time you change the input and lose your focus)
You don't have to create Element in the render function. Instead you can create it in the constructor:
class MyComponent extends React.Component {
constructor (props) {
super(props);
this.state = {value : 'start value'};
this.element = HOC(Input);
}
...
And use it in your render function like this:
<span>
<this.element
value={this.state.value}
onChange={this.onChange.bind(this)}
/>
</span>
If needed you can update this.element in componentWillReceiveProps() or componentWillUpdate().
UPDATE: fiddle
This is the way how I extended a functional component with a controlled input element via children and it does not lose focus.
/* import React, { Fragment, useState } from 'react' */
const { Fragment, useState } = React
/* HOC */
const withInput = Base => (props) => {
const [inputValue, setValue] = useState()
return <Base children={
<Fragment>
{props.children}<br />
<input
value={inputValue}
onChange={({ target: { value }}) => setValue(value)}
placeholder="input from hoc" /><br />
<span>inputValue: {inputValue}</span>
</Fragment>
} />
}
/* Component */
const MyComponent = ({children}) => (
<span>
<span>own elements</span><br />
{children}
</span>
)
const MyComponentWithInput = withInput(MyComponent)
ReactDOM.render(<MyComponentWithInput>passed children</MyComponentWithInput>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Related
Good day. I'm building a tree of components and want to use functions of root component in other components of tree. I throw function reference through all tree.
Also I use the object if me need get value from the function in not root componet.
Can you help me?
Can you show me how to do this as HOC ?
If it will be not so hard for you show examples on my code.
import React from 'react';
class Page extends React.Component{
Answer = {
value : ''
}
doSomething(){
console.log(this.Answer.value);
console.log('Ready!');
}
render(){
return(
<div>
<div>
<Body
ParentFunc={()=>this.doSomething()}
ParentParameters={this.Answer}
/>
</div>
</div>
)
}
}
export default Page
class Body extends React.Component{
render(){
const{
ParentFunc,
ParentParameters
} = this.props
return(
<div>
<div>
<SomeComponent
ParentFunc={()=>ParentFunc()}
ParentParameters={ParentParameters}
/>
</div>
</div>
)
}
}
class SomeComponent extends React.Component{
getAnswer(){
const{
ParentFunc,
ParentParameters
} = this.props
ParentParameters.value = 'Some text'
ParentFunc()
}
render(){
return(
<div onClick={()=>this.getAnswer()}>
We can?
</div>
)
}
}
I don't believe a Higher Order Component alone will solve your basic issue of prop drilling. A React Context would be a better fit for providing values and functions generally to "want to use functions of root component in other components of tree".
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
In a typical React application, data is passed top-down (parent to
child) via props, but such usage can be cumbersome for certain types
of props (e.g. locale preference, UI theme) that are required by many
components within an application. Context provides a way to share
values like these between components without having to explicitly pass
a prop through every level of the tree.
Start by creating your Context and Provider component:
const QnAContext = React.createContext({
answer: {
value: ""
},
doSomething: () => {}
});
const QnAProvider = ({ children }) => {
const answer = {
value: ""
};
const doSomething = () => {
console.log(answer.value);
console.log("Ready!");
};
return (
<QnAContext.Provider value={{ answer, doSomething }}>
{children}
</QnAContext.Provider>
);
};
Render QnAProvider in your app somewhere wrapping the React subtree you want to have access to the values being provided:
<QnAProvider>
<Page />
</QnAProvider>
Consuming the Context:
Class-based components consume contexts via the render props pattern.
<QnAContext.Consumer>
{({ answer, doSomething }) => (
<SomeComponent doSomething={doSomething} answer={answer}>
We can?
</SomeComponent>
)}
</QnAContext.Consumer>
Functional components can use the useContext React hook
const SomeComponent = ({ children }) => {
const { answer, doSomething } = useContext(QnAContext);
getAnswer = () => {
answer.value = "Some text";
doSomething();
};
return <div onClick={this.getAnswer}>{children}</div>;
};
Here is where using a Higher Order Component may become useful. You can abstract the QnAContext.Consumer render props pattern into a HOC:
const withQnAContext = (Component) => (props) => (
<QnAContext.Consumer>
{(value) => <Component {...props} {...value} />}
</QnAContext.Consumer>
);
Then you can decorate components you want to have the context values injected into.
const DecoratedSomeComponent = withQnAContext(SomeComponent);
...
<DecoratedSomeComponent>We can with HOC?</DecoratedSomeComponent>
Note: The point of doing all this was to move the values and functions that were previously defined in Page into the Context, so they are no longer passed from Page though Body to SomeComponent (or any other children components).
Demo
Sandbox Code:
const QnAContext = React.createContext({
answer: {
value: ""
},
doSomething: () => {}
});
const QnAProvider = ({ children }) => {
const answer = {
value: ""
};
const doSomething = () => {
console.log(answer.value);
console.log("Ready!");
};
return (
<QnAContext.Provider value={{ answer, doSomething }}>
{children}
</QnAContext.Provider>
);
};
const withQnAContext = (Component) => (props) => (
<QnAContext.Consumer>
{(value) => <Component {...props} {...value} />}
</QnAContext.Consumer>
);
class SomeComponent extends React.Component {
getAnswer = () => {
const { doSomething, answer } = this.props;
answer.value = "Some text";
doSomething();
};
render() {
return (
<button type="button" onClick={this.getAnswer}>
{this.props.children}
</button>
);
}
}
const DecoratedSomeComponent = withQnAContext(SomeComponent);
class Body extends React.Component {
render() {
return (
<div>
<div>
<QnAContext.Consumer>
{({ answer, doSomething }) => (
<SomeComponent doSomething={doSomething} answer={answer}>
We can?
</SomeComponent>
)}
</QnAContext.Consumer>
</div>
<div>
<DecoratedSomeComponent>We can with HOC?</DecoratedSomeComponent>
</div>
</div>
);
}
}
class Page extends React.Component {
render() {
return (
<div>
<div>
<Body />
</div>
</div>
);
}
}
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<QnAProvider>
<Page />
</QnAProvider>
</div>
);
}
Based on your current code I am making the assumption that Body does not modify the values of ParentFunc and ParentParameters before passing them down to SomeComponent.
You have a hierarchy
<Page>
<Body>
<SomeComponent>
</Body>
</Page>
and you want to pass props from Page to SomeComponent without going through Body.
You can do this using children
children is a special prop representing the JSX child elements of the component. We make it so that Body renders the children that it got through props:
class Body extends React.Component{
render() {
return(
<div>
<div>
{this.props.children}
</div>
</div>
)
}
}
We set that children prop by using a <SomeComponent/> element inside of the <Body>:
render() {
return (
<div>
<div>
<Body>
<SomeComponent
ParentFunc={() => this.doSomething()}
ParentParameters={this.Answer}
/>
</Body>
</div>
</div>
);
}
Note that you cannot directly modify the value that you got from the parent, which is what you were doing with ParentParameters.value = 'Some text'. If you want to update the state of the parent then you need to do that through your callback function props. So your code should look something like this:
import React from "react";
class Body extends React.Component {
render() {
return (
<div>
<div>{this.props.children}</div>
</div>
);
}
}
class SomeComponent extends React.Component {
state = {
showAnswer: false
};
onClick() {
// update answer in parent
this.props.setAnswer("Some text");
// change state to reveal answer
this.setState({ showAnswer: true });
}
render() {
return (
<div>
{this.state.showAnswer && <div>Answer is: {this.props.answer}</div>}
<div onClick={() => this.onClick()}>We can?</div>
</div>
);
}
}
class Page extends React.Component {
state = {
value: ""
};
render() {
return (
<div>
<div>
<Body>
<SomeComponent
answer={this.state.value}
setAnswer={(answer) => this.setState({ value: answer })}
/>
</Body>
</div>
</div>
);
}
}
export default Page;
I am following a reactjs tutorial on higher order components(HOC). I want a HOC to log props when props change.
import React, { useState } from 'react';
function logProps(WrappedComponent) {
return class extends React.Component {
componentDidMount() {
console.log("Component was mounted");
}
componentDidUpdate(prevProps) {
console.log("Current props: ", this.props);
console.log("Previous props: ", prevProps);
}
render() {
// Wraps the input component in a container, without mutating it. Good!
return <WrappedComponent {...this.props} />
}
}
}
class CustomDivElement extends React.Component{
render(){
return <div>{this.props.text}</div>
}
}
function App(props) {
const [text, setText] = useState("");
const EnhancedComponent = logProps(CustomDivElement);
return (
<div tabIndex="0" className="App ui container">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)} />
<EnhancedComponent text={text} />
</div>
)
}
export default App
At first I thought it's because I am using a HOC. So I introduced another lifecycle method which is componentDidMount and it's firing. The componentDidUpdate is not firing, why is that?
Because you unmount the component on every render, the component doesn't get to componentDidUpdate lifecycle.
function App(props) {
// Remount on every render
const EnhancedComponent = logProps(CustomDivElement);
...
}
Instead, when using HOC, or any other Component, you want to mount it once:
// export default logProps(CustomDivElement)
const EnhancedComponent = logProps(CustomDivElement);
function App(props) {
const [text, setText] = useState("");
return (
<div tabIndex="0" className="App ui container">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)} />
<EnhancedComponent text={text} />
</div>
)
}
I'm holding an array of json inside my main component, and I've created an "FormComponent" which hold the the inputs.
I've created an 'add' function inside the parent component and I'm trying to send it to child via prop under the name createTODOhander as you can see here:
import * as React from "react";
import Task from "./Task";
import FormComponent from "./FormComponent";
class TodoList extends React.Component {
state = {
MAXIMUMJOBS: 5,
TodoList: [],
}
createTODO = (Todoname) => {
this.setState({
TodoList: this.state.TodoList.concat({Todoname: Todoname})
})
}
render() {
return (
<div style={{marginLeft: '10px'}}>
<h3>You can enter {this.state.MAXIMUMJOBS - this.state.TodoList.length} TODO list jobs.</h3>
<FormComponent createTODOhandler={this.createTODO()} />
)
}
}
On the FormComponent I'm trying to use it this way:
import * as React from "react";
class FormComponent extends React.Component {
state = {
inputValue: ''
}
inputChanged = (e) => {
this.setState({
inputValue: e.target.value
})
};
createTODO = () => {
if (this.state.inputValue === '') {}//do nothing, not gonna happen because the button is disabled.
else{
this.props.createTODOhandler(this.state.inputValue);
}
};
render() {
return (
<div>
<input type="text" onChange={this.inputChanged} value={this.state.inputValue}
placeholder={"Enter your job here."}/>
<button onClick={this.createTODO}>Add!</button>
<button>Clear list</button>
</div>
)
}
}
export default FormComponent;
But I get an Maximum update depth exceeded. Error,
Thanks for your help.
Should have remove () from the props.
This <FormComponent createTODOhandler={this.createTODO()} />
Changed to: <FormComponent createTODOhandler={this.createTODO} />
This is your bug:
<FormComponent createTODOhandler={this.createTODO()} />
You are executing the function each time you render the component. You need to only have reference to the function instead:
<FormComponent createTODOhandler={this.createTODO} />
-I am using function component.
-for now I am using 3 components here, from that One is parent component and another 2 are child components.
-I need to access one child component methods or state to another child methods. I already done with class components with CreateRef but for now I need to use with function components but I am getting Null inside 'ref.current'.
export function SideBySideList(props) {
const ref = React.createRef();
//this is call inside ListPage after sucess
function updateRightList(id) {
ref.current.state.actualSearchedModel.Id = id
ref.current.fetchDataAndUpdate();
}
function itemClicked(id) {
updateRightList(id);
}
return (
<>
<div className="col-12 no-padding">
<div className={props.leftListLayoutClass}>
<ListPage
updateRightList={updateRightList}
/>
</div>
<div className={props.rightListLayoutClass}>
<ListPage
ref={ref}
/>
</div>
</div>
<>
);
}
According to the official documentation:
You may not use the ref attribute on function components because they
don’t have instances
So if your ListPage is functional component, you have to convert it to the class component. Or your ref must refer to the DOM element inside of ListPage.
function ListPage ({ref}) {
return <div ref={ref}>Hello!</div>
}
UPDATED:
function ParentComponent () {
const [state, setState] = React.useState(null);
const onChildMount = React.useCallback((dataFromChild) => {
setState(dataFromChild);
});
return (
<div>
<pre>{JSON.stringify(state, null, 2)}</pre>
<ChildComponent onMount={onChildMount} />
</div>
)
}
function ChildComponent (props) {
const thisShouldBePassedToTheParent = "from child with love";
React.useEffect(() => {
props.onMount(thisShouldBePassedToTheParent);
}, []);
return (
<div>child component</div>
)
}
ReactDOM.render(<ParentComponent />, document.querySelector("#root"));
<script src="https://unpkg.com/react#16.9.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16.9.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
With functional components you can use refs like this:
// Import our hooks
import React, { useRef, useEffect } from 'react';
// create our ref
const myInput = useRef();
// This is equivalent to our componentDidMount, this will focus
useEffect(() => myInput.current && myInput.current.focus());
// Parse our ref to our textField
<Textfield inputRef={myInput} />
Here you can read docs https://reactjs.org/docs/hooks-reference.html#useref
Also you may use refs like this directly:
<TextField inputRef={input => input && input.focus()} />
You can read full Article here: https://medium.com/javascript-in-plain-english/react-refs-both-class-and-functional-components-76b7bce487b8
If someone looking for solution where Parent is class component and Child is functional component, and want to get data from child (state, function)
Class component:
class Parent extends React.Component {
constructor(){
this.setReferenceToElement = element => {
this.fromChild = element;
}
}
handleClick(){
console.log(this.fromChild());
}
render(){
return (
<div>
<Child setRef={this.setReferenceToElement} />
<button onClick={handleClick}> Get state from child </button>
</div>
)
}
}
Functional component:
function Child(props){
// ... it has some state
props.setRef(state => state);
return <div> Test </div>
}
I'm having trouble understanding how to pass state as props to other child components in React. In my code, you can see I've got a component that takes input and maps it to my state array, displaying part of that data in another component, that's working just fine.
But the overall goal is that when a user clicks on an item they've added to the list, React Router kicks in and changes the view to the MovieDetails component, which will have extra information they've entered, like title, date and description.
I haven't even gotten to setting up react router because I can't seem to properly access state within the MovieDetails component. And then I'm not quite sure how to display the correct MovieDetails component with router.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import './App.css';
class App extends Component {
constructor() {
super();
this.addMovie = this.addMovie.bind(this);
this.state = {
movies : []
};
}
addMovie(movie) {
let movies = this.state.movies;
movies.push(movie);
this.setState({ movies });
}
render() {
return (
<div className="wrapper">
<div className="container">
<div>
<h3 className="heading">Favorite Movies</h3>
</div>
</div>
<div>
<AddMovie addMovie={ this.addMovie }/>
<MovieList movies={ this.state.movies }/>
</div>
</div>
)
}
}
class AddMovie extends Component {
addMovie(event) {
event.preventDefault();
const movie = {
title : this.title.value,
year : this.year.value,
image : this.image.value,
desc : this.desc.value
}
this.props.addMovie(movie);
this.movieForm.reset();
}
render() {
return (
<div className="container">
<form ref={(input) => this.movieForm = input} onSubmit={(e) => this.addMovie(e)}>
<input ref={(input) => this.title = input} className="Input" type="text" placeholder="Title"/>
<input ref={(input) => this.year = input} className="Input" type="text" placeholder="Year"/>
<textarea ref={(input) => this.desc = input} className="Input" type="text" placeholder="Description"></textarea>
<input ref={(input) => this.image = input} className="Input" type="text" placeholder="Poster URL"/>
<button type="submit">Add</button>
</form>
</div>
)
}
}
class MovieList extends Component {
render() {
return (
<div>
{ this.props.movies.map( (movie, i) => <MovieListItem key={i} details={ movie }/> )}
</div>
);
}
}
class MovieListItem extends Component {
constructor(props) {
super(props);
this.toggleClass = this.toggleClass.bind(this);
this.state = {
active: false
};
}
toggleClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
const { details } = this.props;
return (
<div
className={this.state.active ? "red": null}
onClick={this.toggleClass}
>
<img src={details.image} alt=""/>
<hr/>
</div>
)
}
}
class MovieDetails extends Component {
render() {
return (
<div>
<p>title here</p>
<p>year here</p>
<p>description here</p>
<img src="image" alt=""/>
</div>
)
}
}
export default App;
The problem come from the way you try to access the input values. When you use ref, you get a React wrapper, not the real DOM element, so you can't access directly to .value or .reset(). You have to use the getDOMNode() method to get the DOM element. This worked for me :
const movie = {
title : this.title.getDOMNode().value,
year : this.year.getDOMNode().value,
image : this.image.getDOMNode().value,
desc : this.desc.getDOMNode().value
};
...
this.movieForm.getDOMNode().reset();
An other thing, when you setState something that uses the current state, you should use the callback instead :
addMovie(newMovie) {
this.setState(({movies: prevMovies})=> ({
movies: [...prevMovies, newMovie]
}));
}
See complete setState API from official doc
If I got it right, do you want to push to a new component (where the details should be accessible) when you're clicking on an item created from MovieList? If so, here are the steps you have to do:
If you want to push a new view you have to use something like browserHistory or hashHistory from 'react-router'. In this case I'll use browserHistory.
To access the state in MovieDetails component simply pass it through browserHistory.
Here is the way I used your code to push to a new view when an item from MovieList component is clicked:
import {Router, Route, browserHistory} from "react-router";
class Routes extends Component {
render() {
let props = this.props;
return (
<Router history={browserHistory}>
<Route path="/" component={App}/>
<Route path="/movie-details" component={MovieDetails}/>
</Router>
)
}
}
// Here is your App component
class App extends Component {
// ... your code
}
// ... your other classes
class MovieListItem extends Component {
// ... Constructor
// Here I'm pushing the new route for MovieDetails view
toggleClass(details) {
browserHistory.push({
pathname: '/movie-details',
state: details // pass the state to MovieDetails
});
// ... your code
}
render() {
const {details} = this.props;
return (
<div
// ... your code
onClick={this.toggleClass.bind(this, details)} // pass details to toggleClass()
>
// ... your code
</div>
)
}
}
// Here is your Movie Details component
class MovieDetails extends Component {
console.log('This props: ', this.props.location.state); // The details object should be logged here
// ... your code
}
// Export Routes instead of App
export default Routes;
Hope that helps!