React using multiple dynamic contexts for one component - javascript

I made a little utility function(HOC) that can provide the value of a context to a given component
export function withContext<CT>(Component: any) {
return function ContextPass(Context: React.Context<CT>) {
return function Inner(props: any) {
return (
<>
<Context.Consumer>
{
(values) => (
<>
{React.createElement(Component, { ...props, ...values })}
</>
)
}
</Context.Consumer>
</>
);
}
}
}
where I use it like this:
class DesignerHomeLayout extends React.Component{
...
}
export default withContext<ProfileContextType>(
withStyles(styles)(DesignerHomeLayout)
)(ProfileContext)
It's working fine, but my problems is where I want to wrap the component with many Contexts, I know I can wrap again with withContext utility, but I want to make a utility that can take a component and an array of contexts and provide all contexts values to that one component
So far I've made something like this, but it still not working for me
function makeWrapperContext(Context: any, Component: any) {
return function Inner(props: any) {
return (
<>
<Context.Consumer>
{
(values: any) => (
<>
{React.createElement(Component, { ...props, ...values })}
</>
)
}
</Context.Consumer>
</>
);
}
}
export function withContexts({
component, contexts
}: { component: any, contexts: React.Context<any>[] }) {
return contexts.reduce(makeWrapperContext, component);
}
where the desired usage would be
export default withContexts({
component: withStyles(styles)(DesignerHomeLayout),
contexts: [ProfileContext, LabelsContext]
});
I know it might be close to what the solution is, but I'm not sure about it, my biggest challenge is to pass the component parameter to the reduce function, I know it will not work because when the utility function is invoked, the <component.Consumer> will not exist.
I'm open to any edits to this code, or any new solution you have.
One important note: I can't use functional components with hooks within my project, but I'll welcome any hooks based solution, because that will still benefit me and others in future.

Related

How to pass function and data from component class to stateless class in React Native?

I am working with react native and I want to pass function and some data from Component class to another Stateless class, but I could not make to passing function and data part.
Here you can see my Component class:
class Classroom extends Component {
constructor(props, context) {
super(props, context);
};
state = {
isLightOn: false,
title : "Turn light on "
}
onPress() {
this.setState({isLightOn: !this.state.isLightOn})
console.log(this.state.isLightOn)
this.setState({title:this.state.isLightOn===false ?"Turn light off":"Turn light on"})
}
render() {
return (
<View style={styles.blue}>
<LightBulb isLightOn={this.state.isLightOn}> </LightBulb>
<LightButton onPress={this.onPress} isLightOn={this.state.isLightOn} title={this.state.title} > </LightButton>
</View>
);
}
}
Firstly, I want to pass isLightOn and title datas to my LightButton class (which mean to my stateless class). After that, I want to use onPress function inside of my Stateless class, but I cannot use. I am taking that error:
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I also LightButton onPress={this.onPress} remove parenthesis, but still taking error.
Here is my my Stateless class
const LightButton = ({onPress,isLightOn,title}) => (
<View style={styles.red}>
<Button
title= {title}
onPress={() => {}
}
/>
</View>
)
I want to use onPress function and datas inside of the this class.
As a result, How can I pass function and data to that class?
The main issue here is that you need to declare onPress using an arrow function or bind it to the component's this value within the constructor. Otherwise it wouldn't have access to the correct this value. Other than that, the way you were passing props into components is perfectly fine.
I also merged your two set state calls in onPress to one as it's easier.
In LightButton, I set it up like this to pass the onPress function down to the button:
const LightButton = ({ onPress, isLightOn, title }) => (
<div style={{backgroundColor:'red'}}>
<Button title={title} onPress={onPress} />
</div>
);
(I set it up using react, but the issues at hand are more of a JS issue than a React/ReactNative one, so the advice should still be valid :) )
const { Component } = React;
const View = 'div';
const Button = (({title,onPress})=><button onClick={onPress}>{title}</button>);
const LightBulb = ({ isLightOn }) => {
return <div className={'LightBulb' + (isLightOn ? ' on' : '')} />;
};
const LightButton = ({ onPress, isLightOn, title }) => (
<div style={{backgroundColor:'red'}}>
<Button title={title} onPress={onPress} />
</div>
);
class Classroom extends Component {
state = {
isLightOn: false,
title: 'Turn light on ',
};
onPress=()=> {
console.log(this.state.isLightOn);
this.setState({
title:
this.state.isLightOn === false ? 'Turn light off' : 'Turn light on',
isLightOn: !this.state.isLightOn
});
}
render() {
return (
<div style={{backgroundColor:'blue'}}>
<LightBulb isLightOn={this.state.isLightOn}> </LightBulb>
<LightButton
onPress={this.onPress}
isLightOn={this.state.isLightOn}
title={this.state.title}
>Button</LightButton>
</div>
);
}
}
ReactDOM.render(<Classroom />, document.querySelector('#root'));
.LightBulb {
height: 50px;
width: 50px;
border-radius: 50px;
background-color: black;
}
.LightBulb.on {
background-color: white;
}
<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>
<div id="root"/>
You can assign it like
const LightButton = ({onPress,isLightOn,title}) => (
...
onPress={onPress}
...
or with an arrow function if you need to pass arg inside
onPress={()=>onPress(someArg)}
do notice that you either don't put () at all, or twice () => func() for not run the function while it is just loads and not clicked.
unrelated directly to your issue but something that you encounter is inside onPress by doing like so
this.setState({isLightOn: !this.state.isLightOn})
console.log(this.state.isLightOn)
this.setState({title:this.state.isLightOn===false ?"Turn light off":"Turn light on"})
setState it is an async call, and therefore second setState usage not guaranteed to refer the state as you expect, use setState({ ... }, () => callback()) or all at one line and accords to prev state
this.setState({isLightOn: !this.state.isLightOn, title: !this.state.isLightOn===false ?"Turn light off":"Turn light on"})
First thing you did wrong is your state instantiating !
you need to instantiate your state in the constructor block like:
constructor(props, context) {
super(props, context);
this.state = { counter: 0 };
}
onPress() you use this for your function which is not recommended in react native or any other language , those are dedicated functions and methods of React Native
For passing a parameter or calling a function it is better to use these patterns ====>
onPress={() => urFunction()} with parenthesis or
onPress={urFunction} without parenthesis
Do the modifications I hope it helps <3

How to access ref that was set in render

Hi I have some sort of the following code:
class First extends Component {
constructor(props){super(props)}
myfunction = () => { this.card //do stuff}
render() {
return(
<Component ref={ref => (this.card = ref)} />
)}
}
Why is it not possible for me to access the card in myfunction. Its telling me that it is undefined. I tried it with setting a this.card = React.createRef(); in the constructor but that didn't work either.
You are almost there, it is very likely that your child Component is not using a forwardRef, hence the error (from the React docs). ref (in a similar manner to key) is not directly accesible by default:
const MyComponent = React.forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
// ☝️ now you can do <MyComponent ref={this.card} />
ref is, in the end, a DOMNode and should be treated as such, it can only reference an HTML node that will be rendered. You will see it as innerRef in some older libraries, which also works without the need for forwardRef in case it confuses you:
const MyComponent = ({ innerRef, children }) => (
<button ref={innerRef}>
{children}
</button>
));
// ☝️ now you can do <MyComponent innerRef={this.card} />
Lastly, if it's a component created by you, you will need to make sure you are passing the ref through forwardRef (or the innerRef) equivalent. If you are using a third-party component, you can test if it uses either ref or innerRef. If it doesn't, wrapping it around a div, although not ideal, may suffice (but it will not always work):
render() {
return (
<div ref={this.card}>
<MyComponent />
</div>
);
}
Now, a bit of explanation on refs and the lifecycle methods, which may help you understand the context better.
Render does not guarantee that refs have been set:
This is kind of a chicken-and-egg problem: you want the component to do something with the ref that points to a node, but React hasn't created the node itself. So what can we do?
There are two options:
1) If you need to pass the ref to render something else, check first if it's valid:
render() {
return (
<>
<MyComponent ref={this.card} />
{ this.card.current && <OtherComponent target={this.card.current} />
</>
);
}
2) If you are looking to do some sort of side-effect, componentDidMount will guarantee that the ref is set:
componentDidMount() {
if (this.card.current) {
console.log(this.card.current.classList);
}
}
Hope this makes it more clear!
Try this <Component ref={this.card} />

Get containing component from nested component

I am writing a ControlledInput component, and in order to have access to the state of the component using ControlledInput, I have a binder prop in ControlledInput.
I'm having a slight issue when using the component:
render() {
const CI = props => <ControlledInput binder={this} {...props} />;
return (
<div style={styles.container}>
<h1>NEW RECIPE</h1>
<ControlledInput binder={this} label={"Title"} />
</div>
);
}
The implementation above works completely fine. However, note the const CI I've defined. I tried to use this so I could just write <CI label={"Title"}/> without the binder since the binder will be the same on all the ControlledInput components I use in a given render method.
The problem with using <CI label={"Title"}/> is that when I type into the input, the input "blurs" and I have to reselect it. This appears to be because the render method creates the CI on every render.
I hope I've explained that clearly, because my head hurts.
Anyway, it makes sense to me why this happens. And I know that one solution is to put const CI = props => <ControlledInput binder={this} {...props} />; outside of the render function. But then I'd have to call it as <this.CI> and that starts to defeat the purpose.
And I can't put CI in global scope because then I don't have access to this.
Is there a way to solve this?
Update
Here is the current (very much in progress) code for ControlledInput:
// #flow
import React, { Component } from "react";
type Props = {
containerStyle?: Object,
label: string,
propName?: string,
binder: Component<Object, Object>,
onChange?: Object => void
};
class ControlledInput extends Component<Props> {
render() {
const props = this.props;
const propName = props.propName || props.label.toLowerCase();
return (
<div style={props.containerStyle}>
<p>{props.label}</p>
<input
type="text"
label={props.label}
onChange={
this.props.onChange ||
(e => {
props.binder.setState({ [propName]: e.target.value });
})
}
value={props.binder.state[propName]}
></input>
</div>
);
}
}
The point of this whole endeavor is to simplify creating a form with controlled components, avoiding having to add value={this.state.whatever} and onChange={e=>this.setState({whatever: e})} to each one, which is not DRY in my opinion.
And then I want get a little more DRY by not passing binder={this} to every component and that's why I'm doing const CI = props => <ControlledInput binder={this} {...props} />;, which, again, has to be inside the class to access this and inside the render function to be called as CI rather than this.CI.
So that first explanation why you need to pass this, although I suppose I could also have props like setState={this.setState} parentState={this.state}, and in that case it does indeed start to make sense to combine those into something like {...propsToSend} as #John Ruddell suggested.
Note that I've provided a possibility to override onChange, and plan on doing so for most or all of the other props (e.g, value={this.props.value || binder.state[propName]}. If one were to override a lot of these (especially value and onChange) it would indeed make the component much less reusable, but the main use case is for quickly creating multiple inputs that don't have special input handling.
So, again, my ideal would be to call <ControlledInput label="Title"/> and have the component code take care of binding state and setState correctly. If this is possible. And then the second option would be to have a place to define the necessary context props in a place that makes it simple when it's time to actually use the component multiple times, like so:
<ControlledInput label={"title"} {...contextProps}/>
<ControlledInput label={"author"} {...contextProps}/>
<ControlledInput label={"email"} {...contextProps}/>
<ControlledInput label={"content"} textArea={true} {...contextProps}/> // textarea prop not implemented yet, fyi
etc
I hear that accessing the parent state/context may be an anti-pattern, but there must be some way to do what I'm trying to do without using an anti-pattern, isn't there?
If you want the state of the parent, handle the state there and pass down the value to your input - ControlledInput won't have to know anything except how to handle data in and out. Something like this, and note that I jacked up the names a little so you can see which component is handling what:
import React, { useState } from "react"
const Parent = () => {
const [title, setTitle] = useState("")
const handleChangeInParent = (newTitle) => {
setTitle((oldValue) => newTitle)
}
return(<div style={styles.container}>
<h1>NEW RECIPE</h1>
<ControlledInput handleChange={handleChangeInParent} label={title} />
</div>)
}
const ControlledInput = ({handleChange, label}) => {
return (
<input onChange={handleChange} type="text" value={label} />
)
}
If ControlledComponent needs to handle its own state, then pass it a default value and then have the Parent read the value when saving (or whatever):
import React, { useState } from "react"
const Parent = () => {
const handleSaveInParent = (newTitle) => {
console.log("got the new title!")
}
return (
<div style={styles.container}>
<h1>NEW RECIPE</h1>
<ControlledInput handleSave={handleSaveInParent} initialLabel="Title" />
</div>
)
}
const ControlledInput = ({ handleSave, initialLabel }) => {
const [title, setTitle] = useState(initialLabel)
const handleChange = (ev) => {
const value = ev.target.value
setTitle((oldValue) => value)
}
const handleSubmit = (ev) => {
ev.preventDefault()
handleSave(title)
}
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} type="text" value={title} />
</form>
)
}
You shouldn't be sending this through - just send values and/or functions to handle values.
With Updated Implementation
(okay, John you win!)
Not positive if this is technically an "answer", but I've rewritten the component to take a state and (updated) a setterFn prop:
component
// #flow
import React, { Component } from "react";
type Props = {
containerStyle?: Object,
labelStyle?: Object,
label: string,
propName?: string,
state: Object,
onChange?: Object => void,
textArea?: boolean,
setterFn: (key: string, value: mixed) => void
};
class ControlledInput extends Component<Props> {
render() {
const props = this.props;
const propertyName = props.propName || props.label.toLowerCase();
const TagType = props.textArea ? "textarea" : "input";
// only pass valid props to DOM element (remove any problematic custom props)
const { setterFn, propName, textArea, ...domProps } = props;
return (
<div style={props.containerStyle}>
<p style={props.labelStyle}>{props.label}</p>
<TagType
{...domProps}
label={props.label} // actually could get passed automatically, but it's important so I'm leaving it in the code
onChange={
this.props.onChange ||
(setterFn ? e => setterFn(propertyName, e.target.value) : null)
}
value={props.state[propertyName] || ""}
></TagType>
</div>
);
}
}
export default ControlledInput;
in use (somehow less code than before!)
class Wrapper extends Component<Object, Object> {
state = {};
render() {
const setterFn = (k, v) => this.setState({ [k]: v });
const p = { state: this.state, setterFn: setterFn.bind(this) };
return <ControlledInput {...p} {...this.props.inputProps} />
}
}
I guess this is more appropriate. It still takes up a lot more space than binder={this}.
It doesn't actually the questions of:
How to access the parent's state from the component. Though from comments it seems like this is an anti-pattern, which I do understand from the theory of React.
How to set these repeating props elsewhere so that I can just call `. I guess the only solution is to do something like this:
render() {
const props = {state: this.state, setState: this.setState}
<ControlledInput {...props} label="Title"/>
}
Which certainly isn't such a bad solution. Especially if I shorten that name to, say, a single character.
Much thanks to #John Ruddell for setting me on the right path.

Why do I have to use return in a map closure in ES6?

I'm super new in React Js and I'm confusing in map method in react component. I'm making movie list app using react, and declared array of Object named movies. I wanted to use map method to pass props to child component.
But, when I compiled without return() in map method, I got error like this.
import React, { Component } from 'react';
import './App.css';
import Movie from './Movie.js';
const movies = [
{
title:"Oldboy",
poster:"https://upload.wikimedia.org/wikipedia/en/thumb/b/bb/Oldboy_2013_film_poster.jpg/220px-Oldboy_2013_film_poster.jpg"
},
{
title:"Matrix",
poster:"http://www.whatisthematrix.com/img/matrix.jpg"
},
{
title:"Full Metal Jacket",
poster:"https://images-na.ssl-images-amazon.com/images/I/41MN0ANVJTL._SY445_.jpg"
},
{
title:"Hunger Game",
poster:"https://m.media-amazon.com/images/M/MV5BMjA4NDg3NzYxMF5BMl5BanBnXkFtZTcwNTgyNzkyNw##._V1_.jpg"
}
];
class App extends React.Component {
render() {
return (
<div className="App">
{movies.map(movie => {
<Movie title= {movie.title} poster={movie.poster}/>
})};
</div>
);
}
}
export default App;
But, When I compiled it with return() in map method, it runned successfully. As I know, there is no need to use return in ES6. Can you tell me why this error occured?
There is a need to use return with ES6 arrow functions, otherwise it would be impossible for them to not return anything.
return keyword can be omitted with implicit return with no braces:
movies.map(movie => <Movie title= {movie.title} poster={movie.poster}/>)
Parentheses can be used for readability:
movies.map(movie => (
<Movie title= {movie.title} poster={movie.poster}/>
))
Implicit return works only if there is no block {}
when you use {} after arrow you need to use return
{movies.map(movie => {
<Movie title= {movie.title} poster={movie.poster}/>
})};
Or you can do it using ()
{movies.map(movie => (
<Movie title= {movie.title} poster={movie.poster}/>
))};
In ES6 if a function has to return only value than you can write like this:
{
movies.map((item) => <Movies title={item.title} poster={item.poster}/>);
}
If a function has more task to do you can use like this:
{
movies.map((item) => {
return(
<Movies title={item.title} poster={item.poster}/>
);
});
}

Passing a custom argument to the eventListener in React [duplicate]

We should avoid method binding inside render because during re-rendering it will create the new methods instead of using the old one, that will affect the performance.
So for the scenarios like this:
<input onChange = { this._handleChange.bind(this) } ...../>
We can bind _handleChange method either in constructor:
this._handleChange = this._handleChange.bind(this);
Or we can use property initializer syntax:
_handleChange = () => {....}
Now lets consider the case where we want to pass some extra parameter, lets say in a simple todo app, onclick of item i need to delete the item from array, for that i need to pass either the item index or the todo name in each onClick method:
todos.map(el => <div key={el} onClick={this._deleteTodo.bind(this, el)}> {el} </div>)
For now just assume that todo names are unique.
As per DOC:
The problem with this syntax is that a different callback is created
each time the component renders.
Question:
How to avoid this way of binding inside render method or what are the alternatives of this?
Kindly provide any reference or example, thanks.
First: A simple solution will be to create a component for the content inside a map function and pass the values as props and when you call the function from the child component you can pass the value to the function passed down as props.
Parent
deleteTodo = (val) => {
console.log(val)
}
todos.map(el =>
<MyComponent val={el} onClick={this.deleteTodo}/>
)
MyComponent
class MyComponent extends React.Component {
deleteTodo = () => {
this.props.onClick(this.props.val);
}
render() {
return <div onClick={this.deleteTodo}> {this.props.val} </div>
}
}
Sample snippet
class Parent extends React.Component {
_deleteTodo = (val) => {
console.log(val)
}
render() {
var todos = ['a', 'b', 'c'];
return (
<div>{todos.map(el =>
<MyComponent key={el} val={el} onClick={this._deleteTodo}/>
)}</div>
)
}
}
class MyComponent extends React.Component {
_deleteTodo = () => {
console.log('here'); this.props.onClick(this.props.val);
}
render() {
return <div onClick={this._deleteTodo}> {this.props.val} </div>
}
}
ReactDOM.render(<Parent/>, document.getElementById('app'));
<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="app"></div>
EDIT:
Second: The other approach to it would be to use memoize and return a function
constructor() {
super();
this._deleteTodoListener = _.memoize(
this._deleteTodo, (element) => {
return element.hashCode();
}
)
}
_deleteTodo = (element) => {
//delete handling here
}
and using it like
todos.map(el => <div key={el} onClick={this._deleteTodoListener(el)}> {el} </div>)
P.S. However this is not a best solution and will still result in
multiple functions being created but is still an improvement over the
initial case.
Third: However a more appropriate solution to this will be to add an attribute to the topmost div and get the value from event like
_deleteTodo = (e) => {
console.log(e.currentTarget.getAttribute('data-value'));
}
todos.map(el => <div key={el} data-value={el} onClick={this._deleteTodo}> {el} </div>)
However, in this case the attributes are converted to string using toString method and hence and object will be converted to [Object Object] and and array like ["1" , "2", "3"] as "1, 2, 3"
How to avoid this way of binding inside render method or what are the
alternatives of this?
If you care about re-rendering then shouldComponentUpdate and PureComponent are your friends and they will help you optimize rendering.
You have to extract "Child" component from the "Parent" and pass always the same props and implement shouldComponentUpdate or use PureComponent. What we want is a case when we remove a child, other children shouldn't be re-rendered.
Example
import React, { Component, PureComponent } from 'react';
import { render } from 'react-dom';
class Product extends PureComponent {
render() {
const { id, name, onDelete } = this.props;
console.log(`<Product id=${id} /> render()`);
return (
<li>
{id} - {name}
<button onClick={() => onDelete(id)}>Delete</button>
</li>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
products: [
{ id: 1, name: 'Foo' },
{ id: 2, name: 'Bar' },
],
};
this.handleDelete = this.handleDelete.bind(this);
}
handleDelete(productId) {
this.setState(prevState => ({
products: prevState.products.filter(product => product.id !== productId),
}));
}
render() {
console.log(`<App /> render()`);
return (
<div>
<h1>Products</h1>
<ul>
{
this.state.products.map(product => (
<Product
key={product.id}
onDelete={this.handleDelete}
{...product}
/>
))
}
</ul>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Demo: https://codesandbox.io/s/99nZGlyZ
Expected behaviour
<App /> render()
<Product id=1... render()
<Product id=2... render()
When we remove <Product id=2 ... only <App /> is re-rendered.
render()
To see those messages in demo, open the dev tools console.
The same technique is used and described in article: React is Slow, React is Fast: Optimizing React Apps in Practice by François Zaninotto.
Documentation encourages to use data-attributes and access them from within evt.target.dataset:
_deleteTodo = (evt) => {
const elementToDelete = evt.target.dataset.el;
this.setState(prevState => ({
todos: prevState.todos.filter(el => el !== elementToDelete)
}))
}
// and from render:
todos.map(
el => <div key={el} data-el={el} onClick={this._deleteTodo}> {el} </div>
)
Also note that this makes sense only when you have performance issues:
Is it OK to use arrow functions in render methods?
Generally speaking, yes, it is OK, and it is often the easiest way to
pass parameters to callback functions.
If you do have performance issues, by all means, optimize!
This answer https://stackoverflow.com/a/45053753/2808062 is definitely exhaustive, but I'd say fighting excessive re-renders instead of just re-creating the tiny callback would bring you more performance improvements. That's normally achieved by implementing a proper shouldComponentUpdate in the child component.
Even if the props are exactly the same, the following code will still re-render children unless they prevent it in their own shouldComponentUpdate (they might inherit it from PureComponent):
handleChildClick = itemId => {}
render() {
return this.props.array.map(itemData => <Child onClick={this.handleChildClick} data={itemData})
}
Proof: https://jsfiddle.net/69z2wepo/92281/.
So, in order to avoid re-renders, the child component has to implement shouldComponentUpdate anyway. Now, the only reasonable implementation is completely ignoring onClick regardless of whether it has changed:
shouldComponentUpdate(nextProps) {
return this.props.array !== nextProps.array;
}

Categories

Resources