React context props drilling, what is it that I don't get? - javascript

There is a TL;DR at the bottom.
I am probably doing this wrong or using the context in an bad way. I am new to react so I have no clue if this is how we are meant to do things.
My understanding:
Context can be used to pass down props to deeper nested child components without having to pass them through all levels of nesting. A provider is filled with props, and a consumer will look "up the tree" to find the nearest provider and get it's data.
If this is the case, then I can load a provider with a function, such as an onChange handler in order to avoid having to write the handler on every child component when they all do the same thing. This would allow for a "smart form" which govern's its input's handlers by "passing" handlers given to it. Obviously just writing one handler on multiple components is not an issue, but having like 20-30 form fields and writing 4+ handlers on each of them just creates code clutter. So I tried the following:
HTML structure is like this, for example:
<ControlledForm data={some_data} handlers={some_handlers}>
<LabeledControl name="Type your name" rel="Name" meta={{some_meta_object}}></LabeledControl>
<LabeledControl name="Pet name" rel="PetName" meta={{some_meta_object}}></LabeledControl>
<LabeledControl name="Type of pet" rel="PetType" meta={{some_meta_object}}></LabeledControl>
<LabeledControl name="Family" rel="Family" meta={{some_meta_object}}></LabeledControl>
</ControlledForm>
And this is the ControlledForm class code:
const { Provider } = React.createContext(); //Note this
class ControlledForm extends Component {
state = {};
render() {
return (
<Provider value={{ onChange: this.props.onChange }}>
<form>{this.props.children}</form>
</Provider>
);
}
}
Now whatever child I place within this form would want to have a <Consumer> wrapper around it to consume the changeHandler, or at least this is the plan. However when I wrap my LabeledControl in a consumer, it acts as if it has no data.
<LabeledControl> (reduced code):
const { Consumer } = React.createContext();
class LabeledControl extends Component {
state = {};
render() {
return (
<Consumer>
{r => {
console.log("consumer:", r); //Logs undefined
return (
<div className="labeled-control">
{/*Code here*/}
</div>
);
}}
</Consumer>
);
}
}
If I was to guess at what the issue is, I'd say it is because both the ControlledForm and the LabeledControl create it's own context, which is not shared, look at the code above. But I do not understand how would I share this context and still keep the two classes in separate .js files. I cannot pass a reference down to the children, all I get is the {this.props.children} and no way to tell it "Hey use this provider here". All the examples I find online have the two classes that are a provider and a consumer in a same file, being able to reference the same "context" but this seriously impacts the freedom of what I can put inside a form, or rather doesn't let me have customization in terms of "children".
TLDR
How do I pass down a "Context" from a Provider to a Consumer when they are in two different javascript files? Code is above. I essentially need to pass down a handler to every child and have it (maybe, maybe not, depending on a child) use the handler to tell the parent to update it's data. All of this whilst using {this.props.children} in a parent in order to allow "outter code" to "inject" the parent component with any children desired and have them either use or not use the parent's handler.
Edit:
I searched about a bit and found two possible solutions, which I both tested and both seem to be working (with a limited use case). Both render props and React.CloneElement seem to do the trick when there is one level of nesting as we can directly render and add props to children with them, but when we need to prop drill several levels, all the components in between would have to implement the same passing of props which then turns to spaghetti code. Still searching to try and find the way to pass the context down to the children for consumption in different files.

Please view the code below.
Also: here is a sample project I have built:https://codesandbox.io/s/5z62q8qnox
import React from 'react'
import PropTypes from 'prop-types';
import 'bootstrap/dist/css/bootstrap.min.css';
export default class ControllerForm extends React.Component {
static childContextTypes = {
onChange: PropTypes.func.isRequired
}
getChildContext() {
return {
onChange: this.handleOnChange
}
}
handleOnChange = (e) => {
console.log(e.target.value) //here is the place you have to implement
}
render() {
return (
<div class="container">
{this.props.children}
</div>
)
}
}
import React from 'react'
import PropTypes from 'prop-types';
import 'bootstrap/dist/css/bootstrap.min.css';
export default class LabeledControl extends React.Component {
static contextTypes ={
onChange : PropTypes.func.isRequired
}
render() {
return (
<div>
<div className="form-group">
<input className="form-control" type="text" onChange={this.context.onChange} />
</div>
</div>
)
}
}
function App() {
return (
<div className="App">
<ControllerForm>
<LabeledControl />
<LabeledControl />
</ControllerForm>
</div>
);
}

It appears that Context is not what I should be using for this, instead either render props or React.cloneElement() is the proper solution, despite my best efforts to enforce a context.
Parent's render:
{this.props.children.map((child, index) =>
React.cloneElement(child, { key: index, handler: handler })
)}
Child's render:
return (
<div>
<span onClick={this.props.handler}>{passed.foo}</span>
</div>
);
This way the structure remains clean and handlers get passed down. Only issue is every component that needs to pass them down has to implement this, but it would have been the same with context, since it is not exported to a separate file.

Related

Direct call of a functional component

Stateless functional component is just a function that receives props and returns React element:
const Foo = props => <Bar />;
This way <Foo {...props} /> (i.e. React.createElement(Foo, props)) in parent component could be omitted in favour of calling Foo directly, Foo(props), so React.createElement tiny overhead could be eliminated, yet this isn't necessary.
Is it considered a bad practice to call functional components directly with props argument, and why? What are possible implications of doing this? Can this affect the performance in negative way?
My specific case is that there's some component that is shallow wrapper over DOM element because this was considered a good idea by a third party:
function ThirdPartyThemedInput({style, ...props}) {
return <input style={{color: 'red', ...style}} {...props} />;
}
Here's a demo that shows this case.
This is widely accepted practice but the problem with it is that it's impossible to get ref of wrapped DOM element from stateless function, so the component uses React.forwardRef:
function withRef(SFC) {
return React.forwardRef((props, ref) => SFC({ref, ...props}));
// this won't work
// React.forwardRef((props, ref) => <SFC ref={ref} {...props } />);
}
const ThemedInput = withRef(ThirdPartyThemedInput);
This way it can be used as:
<ThemedInput ref={inputRef} />
...
inputRef.current.focus();
The obvious downside I'm aware of is that withRef requires a developer to be aware of wrapped component implementation, which isn't a usual requirement for HOCs.
Is it considered a proper approach in a situation like described above?
I don't think there's anything wrong with calling Stateless Functional Component directly. As you said it's even one tiny overhead eliminated. As to the possible implications, it would be bold to say that there are none implications and there will be none implications in the future because this is a really rare way of using SFC's. But everything points to conclusion that there shouldn't be any implications (it's just one function call less).
Anyway, below I'd like to present another way of doing this using findDOMNode instead of refs:
I've created Focus component that is really convenient to use but needs to be initialized first (since we need a way to trigger focus outside props since a component may be rerendered with the same props):
// focus.js
import React from "react";
import { findDOMNode } from "react-dom";
export default function createFocus() {
class Focus extends React.Component {
componentDidMount() {
Focus.now = () => {
findDOMNode(this).focus();
}
}
render() {
return this.props.children;
}
}
return Focus;
}
// index.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import createFocus from './focus';
const Focus = createFocus();
import { ThirdPartyThemedInput } from './third-party-lib';
function App() {
return (
<div>
<button onClick={() => Focus.now()}>Proceed with form</button>
<Focus>
<ThirdPartyThemedInput placeholder="Fill me" />
</Focus>
</div>
);
}
render(<App />, document.getElementById('root'));
live at: https://stackblitz.com/edit/react-bpqicw
Functional components are very useful when you don't need to use any of the lifecycle method or don't need to update the component state. As far as you don't need to them, you're good and yet best to go with stateless component.
This will not hit the performance issue but gain the profit regarding its performance because we're just simply using function to render the component and not caring for its update, mounts, receive props, etc. But still there's no 100% gain using stateless component because react internally use class to render them.
It's about 45% improvement.
This post will also guide which one to choose between statefull component and stateless component.
Further, you can not only receive the props but can also receive the ref:
const stateless = (props, ref) => <ReturnComponent {...props} ref={ref} />
Okay, let me refine my statement. Most of the blogs and even the docs states that stateless component don't have ref. Here are a few Q/A prepared regarding this issue:
Do I need to use statefull component just to use ref?
No. I already mentioned that we must require the class based component if we have to work with component state or hook some lifecycle method.
How can I create ref in stateless component?
const stateless = () => {
// we can't do this.myRef = React.createRef()
// so, let's create an object
const RefObj = {}
// now, create ref in {RefObj}
RefObj.myRef = React.createRef()
return <input type="text" ref={myRef} />
}

Storing methods needed in all components

I have a universal app I'm developing for learning purposes. I'm managing the state of my app with Redux, so all my data will be available there. But I want to create some methods that I'm going to use in all my components. The problem is: where should I store this methods?
Adding them to a parent component and passing the methods as props doesn't seem very useful, because this is one of the things that Redux tries to solve. And I'm pretty sure that Redux is not a place for storing methods.
I know I can create a class in a file somewhere, export it, add some methods to it, and when I want to use one method in a component I can call this file, create an instance of the class and call the needed method; but this doesn't look very react to me…
Is there a right way to create methods available for all components?
I've had some success sharing functions between components using an approach similar to the following. I'm not sure this approach will solve your specific use case with regards to cookies, however.
These functions can be stored anywhere and imported wherever required. They accept a component as their first argument, then return a function that operates on the component passed in.
Indicative, untested code follows.
// An event handler than can be shared between multiple components
const handleChange = component => event => component.setState({ value: event.target.value });
class ComponentOne extends PureComponent {
state = {};
render() {
return (
<div>
{this.state.value}
<input onChange={handleChange(this)} />
</div>
);
}
}
class ComponentTwo extends PureComponent {
state = {};
render() {
return (
<div>
{this.state.value}
<input onChange={handleChange(this)} />
</div>
);
}
}

Integrating and rendering react-swipe-cards

I am very noob with reactJs, in fact I just finished this course and am struggling with some concepts here.
I am willing to create an app for people to express their preferences with regards of subjects for a newsletter, and have grabbed a very comprehensive list of topics (2k+) and wanna make some fun way to select them, so I think that something along the lines of Tinder swipeable cards would be a perfect fit, so I am trying to implement this react module functionality into my App.
But it is not showing up anything.
I just created a Repo, in which I had a few tries with no luck.
Basically, the example provided in the module documentation says that it should start by
const data = ['Alexandre', 'Thomas', 'Lucien', 'Raphael', 'Donatello', 'Michelangelo', 'Leonardo']
const Wrapper = () => {
return (
<Cards onEnd={console.log("action('end')")} className='master-root'>
{data.map(item =>
<Card
onSwipeLeft={console.log("action('swipe left')")}
onSwipeRight={console.log("action('swipe right')")}>
<h2>{item}</h2>
</Card>
)}
</Cards>
)
}
But I am completely lost with it, I supposed that it should provide me with a React Component <Something />, but instead it generate something in the lines of a function, that returns a div, which looks a lot with a component, but I have no idea about how integrate into this example.
Note: In the repo graph, I noticed that there is another developer that made some adjustments to make it compatible with react 16.xx.beta, I'v tried it also, no lucky also.
I am almost sure, that there are some concepts I am missing here, so, any reference is more than welcome, also.
What you are looking for is a functional stateless component, the below code
const Wrapper = () => {
return (
<Cards onEnd={console.log("action('end')")} className='master-root'>
{data.map(item =>
<Card
key={item}
onSwipeLeft={() => {console.log("action('swipe left')")}}
onSwipeRight={() => {console.log("action('swipe right')")}}>
<h2>{item}</h2>
</Card>
)}
</Cards>
)
}
is a functional component.
According to documentation
Functional and Class Components
The simplest way to define a component is to write a JavaScript
function:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
This function is a valid React component because it accepts a single
“props” (which stands for properties) object argument with data and
returns a React element. We call such components “functional” because
they are literally JavaScript functions.
The way to render a function component is just like you would render a normal React component
<Wrapper {...props} /> // {...props} is a spread operator syntax to just pass all the props down to wrapper you can pass selected props too
Also react-swipe-card doesn't provide you Wrapper functional component, it provides you components like Cards and Card which you used to render the card view in the Wrapper Component
import Cards, { Card } from 'react-swipe-card'
Now in your case it would look like
export default class MyCards extends Component {
render() {
return <Wrapper />;
}
}
However since you don't have a state and also you are not using lifecycle functions you could simple write the above MyCards Component as
export const MyCards= () => {
return <Wrapper />;
}
I however assume that you would eventually be writing some of the logic there and hence keep it a stateful React component. I have also include the logic whereby you would handle the state change on left or write swipe.
Check a working DEMO
P.S. I a recommendation to go through the React docs thoroughly as they have explained the concepts really well
If I understand you question as suppose. It look you have some small mistake. I download the repo and run you test on React 15.4.2
Your Card component call:
<Card
onSwipeLeft={console.log("action('swipe left')")}
onSwipeRight={console.log("action('swipe right')")}>
<h2>{item}</h2>
</Card>
My Card component call:
<Card
key={item}
onSwipeLeft={()=>{console.log("action('swipe left')")}}
onSwipeRight={() => {console.log("action('swipe right')")}}>
<h2>{item}</h2>
</Card>
We need to create scope for events handler that is why one of the solution is a arrow function. They aren’t just syntactic sugar around anonymous functions though. An arrow function does not have its own context and will instead use the same this as the context in which it was defined. Here is more detail handle-events-in-react-with-arrow-functions
Also on the MyCards you are returning something like (your code)
export default class MyCards extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return Wrapper;
// return (
// <div>
// <p>Something</p>
// {Wrapper();}
// </div>
// );
}
}
But you should return a component and the way is return it have to be
export default class MyCards extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return <Wrapper/>
}
}
Suggestion: If you are not going to have any state in the MyCards component you can make it a functional Component
The current top answer is correct and great about the concept of the functional component. You are actually creating a High Order Component.
However I feel your repo can actually be fixed by just making this change:
render() {
return Wrapper();
}
See that I actually executed the Wrapper function in order to get the Component as a result, otherwise you are rendering a function and not a Component.
You can refactor your code to actually extend a React.Component, and I actually recommend this as HOC are better used for another type of objective, like decorators.
See here, the only thing I changed is that: https://codesandbox.io/s/xp6pzpyoq

react how to call state variable from another class?

Search.js
class Search extends Component {
constructor() {
super();
this.state = {
selectedPictures: []
}
}
static getSelectedPictures = () => {
return this.state.selectedPictures;
}
render() {
return (
<div>
...
</div>
);
}
}
export default Search;
Other.js
import Search from './Search';
class Other extends Component {
constructor() {
super();
this.state = {
}
}
render() {
console.log(Search.getSelectedPictures); --> Uncaught null
return (
<div>
...
</div>
);
}
}
export default Other;
How to call Search.state.selectedPictures inside Other.js?
I already try to use static method to return this.state.selectedPictures and call in Other.js, but cannot access.
Any way can import or transfer the var? Both js files are separate files
Thank you.
What you're trying to do isn't really possible in React for a couple of reasons. First of all, you're trying to call methods and access properties on a class, not on an object. You would, in normal (modern) JS, be required to instantiate the class with the new keyword. For example, search = new Search(); search.getSelectedPictures() - this, however, isn't really how React works, and because your classes are actually components, you have to use the <Search/> component syntax in your render function.
As for getting access to the state in Search, you'd need to pass that state from Search to Other.
One way would be to pass the state into the props directly, so in search.js:
render() {
<Other selectedPictures={this.state.selectedPictures} />
}
Then in other.js:
render() {
this.props.selectedPicture.forEach((pic) => <img src={pic} />);
}
Alternatively, you could have a more umbrella parent component, and keep the state in there. Then pass that state to both components simultaneously, if the ones you list are not meant to have a parent-child relationship.
There are also, albeit slightly more complex, ways of doing what you wish but with Search as a child of Other, but without knowing what those two components actually are, it's hard to really tell.
Use flux architecture . The simple implementation is
alt flux
Just create an Action and a Store . When you select images just put them in the Store using Action then get them as props using <AltContainer />

Communicating between nested child components in React.js

It won't take you long to realise that I am probably out of my depth here. I am not only new to React.js but also to ES6 and so please be gentle with your answers...
Here goes.
I am using a component to build a form input:
const Input = (props) => {
return(
<input ... />
)
}
I have a component which I use to construct HTML around any of the basic form elements that I give it.
const InputWrap = (props) => {
return(
<div class="input-wrap" ...>
{children}
</div>
)
}
Which allows me to do something like this...
const Input = (props) => {
return(
<InputWrap>
<input ... />
</InputWrap>
)
}
What I would like to do is to add a character counting component to the mix:
const InputWrap = (props) => {
return(
<div class="input-wrap" ... >
{children} // which is the HTML input
{props.maxValue && <CharCounter />}
</div>
)
}
So here is my problem...
The <CharCounter /> needs to be notified of any changes happening to the <input /> and update it's internal state.
The <input /> and the <CharCounter /> are siblings and children of the <InputWrap /> and so, from what I can gather, I need a method inside <InputWrap /> which ties an onChange of the <input /> and some method that will update the state within the <CharCount /> component.
I am at a loss as to how I go about adding a callback as the <input onChange={doSomething} /> is in the form {children} by the time it comes in contact with the <CharCount /> once inside the <InputWrap />...
Where am I going wrong here? I'm starting to think it was way back at the beginning...
There are two typical ways of communication between siblings:
You use the InputWrapper as an DataContainer
You use a Data Flow library like flux or redux (which is a lot more complex, especially for this case)
For the 1. you need, as you correctly noticed, an onChange handler for the input component, which is a function defined in the Component and which is passed to the input. If your input component is an own component and not the native component you will need to pass the onChange prop to the native input.
The function in the Component takes the input, counts the chars and sets an internal state variable with setState({ charCount: #CountValue#}). And then you can pass the state variable to the CharCount Component with
One Important thing to mention: You are using stateless components and therefore you will need to change your InputWrap to a normal react component
class InputWrap extends React.Component {
...
}
Hope this will give you the right direction.
Another excellent solution that is like redux but has a different architecture and api is https://github.com/mobxjs/mobx-react.
You can use the inject HOC to inject shared state to any react component in your application.

Categories

Resources