Forwarding Props to React Component flow - reactjs

The basic idea of forwarding props is that I want to enhance an element,
by composing it. E.g.
class MyButton extends Component {
render() {
const { busy, children, ...props } = this.props
return <button { ...props }>
{ busy ? "busy" : children }
</button>
}
}
Here I can add an extra property (busy) to the button element,
but still have all the properties of the button element passed to the
"real" button.
This saves me a lot of writing, since I don't have to write every
property again. Also if the button interface gets changed my button wrapper
will adapt automatically.
Now I want to do the same thing with flow type annotations.
class Button extends Component<props_t, state_t> {
render() { ... }
}
But how should I define the props_t.
If I write
type props_t = { busy: boolean }
this would only accept a busy property.
My idea was to use a union type and do something like this:
import type { ElementProps } from "react"
const button = props => <button { ...props } />
type props_t = {
busy: boolean
} | ElementProps<(typeof button)>
Actually this seams to work, but looks more like a hack because the
props must first be inferred.
Does anybody know a better approach, or how it is typically done?
EDIT:
After some tests I came to the conclusion that the solution above does not work.

Related

react handle this in onClick

I want to control my react-router with the following component:
import React from 'react';
export interface INavigationMenuProps{
routes: string[];
}
export class NavigationMenu extends React.Component<INavigationMenuProps> {
constructor(props: INavigationMenuProps) {
super(props);
}
handleButtonClick(path: string) : void {
//this.props.history.replace(path);
console.log(path);
}
render() {
let self = this;
return (
<div>
{
this.props.routes.map(function(item,id) {
let buttonText = item.replace('/','');
return <button key={id} onClick={ (e) => {self.handleButtonClick(item);}}>{buttonText}</button>
})
}
</div>
)
}
}
This does work. But looking at the render-function made me think if this is a proper solution. I have to safe this to a variable and pass a lambda-function to the on-click-handler of the button which calls the handleButtonClick-function of my this-variable.
Is this really the proper way of handling events?
I also tried this:
return <button key={id} onClick={this.handleButtonClick(item)}>{buttonText}</button>
But this doesn't work because onClick doesn't have the same signature as handleButtonClick
So I had to wrap my function-call and tried this:
return <button key={id} onClick={()=> {this.handleButtonClick(item);}}>{buttonText}</button>
But then I got the following error:
'this' implicitly has type 'any' because it does not have a type
annotation
Can someone explain why this works when I store this to a variable?
Is this really the way-to-do?
In regards to the line let self = this;, the more common approach seen in React codebases is to bind those functions in the component's constructor. So you'll want to add a line like the following at the end of your constructor:
this.handleButtonClick = this.handleButtonClick.bind(this);
You should not run into that "implicit any" error in this case. This pattern is necessary to bind the functions to an instance of the component, which is what you're doing at runtime in render() by assigning this to self.
To answer your numbered questions:
1. My guess here is that TypeScript is able to resolve the type of your variables, but is not resolving this to an instance of the class NavigationMenu.
2. Yes. Your overall pattern here (writing a click handler in the component to handle whatever custom logic you may need) is correct. I see this commonly in React code & write React like that myself. The spot where you're deviating from what I've commonly seen is not binding the function in the constructor.
#michael-langan's answer is correct, but just to be "complete" and propose another solution... you could also take this approach (which #tmeans alludes to in his comment...):
import React from 'react';
export interface INavigationMenuProps{
routes: string[];
}
export class NavigationMenu extends React.Component<INavigationMenuProps> {
constructor(props: INavigationMenuProps) {
super(props);
}
handleButtonClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
const path = event.currentTarget.getAttribute('data-route');
console.log(path);
this.props.history.replace(path);
}
render() {
return (
<div>
{
this.props.routes.map((item, id) => {
let buttonText = item.replace('/','');
return <button key={id} data-route={item} onClick={this.handleButtonClick }>{buttonText}</button>
})
}
</div>
)
}
}
One of the nice things about this approach is that it avoid's creating an arrow function inside of the click handler of the Component, which can sometimes cause performance issues due to the way that React will "recreate" this function each time it renders.
Also, this avoids doing a bind in the constructor, which if you have a lot of click handlers in the same Component, can sometimes be messy to read.
But, all of that is really up to preference, and what linting rules you strictly are trying to follow. Like I said #michael-langan's answer is also perfectly correct.

Is it ok to use a wrapper component to pass props in React?

export function injectProps() {
const injects = {store: new Store()}; // some store
return function (Component) {
return class Proxy extends React.Component {
render() {
return React.createElement(Component, {
...injects,
...this.props,
});
}
};
}
}
Is it ok to use this instead of Redux or Context API with React?
Update: I think I missed to point out my expectation. I'm actually passing some service(http, localStorage) to childrens only when they asks for it. It's not only about the store as services don't have any state. But I also need to pass store through it.
https://pastebin.com/G3PgVxLn
Maybe this tweet by the Dan Abramov (React maintainer) might help.
I understand it was probably not the point of the article. But I see
people reaching for Context or Redux because they don’t realize
components can take any children — and that often removes the need for
deep prop passing. Would be great to highlight!
And Dave Ceddia posted a relavant React documentation link.
Composition vs Inheritance
You can read upon those two.
And here is a demo Nicolas Marcora created to show me how to pass properties to child/children.
You can pass props to children using React.cloneElement(child,...
Working demo on StackBlitz.
export default class WithMouse extends React.Component {
state = { x: 0, y: 0 }
handleMouseMove = event => { ... }
render() {
const { children } = this.props
const childElements = React.Children.map(children, child =>
React.cloneElement(child, {
mouse: this.state,
onMouseMove: this.handleMouseMove
})
)
return <div>
{ childElements }
</div>
}
}
You can use WithMouse class to pass props downward to all children and use it like following.
class App extends Component {
...
render() {
return (
<WithMouse>
<MouseTracker />
</WithMouse>
);
}
}
MouseTracker has access to props passed from WithMouse so you can just use it without directly passing it manually.
You can probably go further and pass all props instead of a few (mouse, onMouseMove)

How to create a generic React component with a typed context provider?

With React's new context API, you can create a typed context producer/consumer like so:
type MyContextType = string;
const { Consumer, Producer } = React.createContext<MyContextType>('foo');
However, say I have a generic component that lists items.
// To be referenced later
interface IContext<ItemType> {
items: ItemType[];
}
interface IProps<ItemType> {
items: ItemType[];
}
class MyList<ItemType> extends React.Component<IProps<ItemType>> {
public render() {
return items.map(i => <p key={i.id}>{i.text}</p>);
}
}
If I instead wanted to render some custom component as the list item and pass in attributes from MyList as context, how would I accomplish that? Is it even possible?
What I've tried:
Approach #1
class MyList<ItemType> extends React.Component<IProps<ItemType>> {
// The next line is an error.
public static context = React.createContext<IContext<ItemType>>({
items: []
}
}
This approach doesn't work because you can't access the class' type from a static context, which makes sense.
Approach #2
Using the standard context pattern, we create the consumer and producer at the module level (ie not inside the class). The problem here is we have to create the consumer and producer before we know their type arguments.
Approach #3
I found a post on Medium that mirrors what I'm trying to do. The key take away from the exchange is that we can't create the producer/consumer until we know the type information (seems obvious right?). This leads to the following approach.
class MyList<ItemType> extends React.Component<IProps<ItemType>> {
private localContext: React.Context<IContext<ItemType>>;
constructor(props?: IProps<ItemType>) {
super(props);
this.localContext = React.createContext<IContext<ItemType>>({
items: [],
});
}
public render() {
return (
<this.localContext.Provider>
{this.props.children}
</this.localContext.Provider>
);
}
}
This is (maybe) progress because we can instantiate a provider of the correct type, but how would the child component access the correct consumer?
Update
As the answer below mentions, this pattern is a sign of trying to over-abstract which doesn't work very well with React. If a were to try to solve this problem, I would create a generic ListItem class to encapsulate the items themselves. This way the context object could be typed to any form of ListItem and we don't have to dynamically create the consumers and providers.
I don't know TypeScript so I can't answer in the same language, but if you want your Provider to be "specific" to your MyList class, you can create both in the same function.
function makeList() {
const Ctx = React.createContext();
class MyList extends Component {
// ...
render() {
return (
<Ctx.Provider value={this.state.something}>
{this.props.children}
</Ctx.Provider>
);
}
}
return {
List,
Consumer: Ctx.Consumer
};
}
// Usage
const { List, Consumer } = makeList();
Overall I think you might be over-abstracting things. Heavily using generics in React components is not a very common style and can lead to rather confusing code.
I had the same problem and I think I solved it in a more elegant way:
you can use lodash once (or create one urself its very easy) to initialize the context once with the generic type and then call him from inside the funciton and in the rest of the components you can use custom useContext hook to get the data:
Parent Component:
import React, { useContext } from 'react';
import { once } from 'lodash';
const createStateContext = once(<T,>() => React.createContext({} as State<T>));
export const useStateContext = <T,>() => useContext(createStateContext<T>());
const ParentComponent = <T>(props: Props<T>) => {
const StateContext = createStateContext<T>();
return (
<StateContext.Provider value={[YOUR VALUE]}>
<ChildComponent />
</StateContext.Provider>
);
}
Child Component:
import React from 'react';
import { useStateContext } from './parent-component';
const ChildComponent = <T>(props: Props<T>) => {
const state = useStateContext<T>();
...
}
Hope it helps someone
I think the answer, unfortunately, is that the question doesn't actually make sense.
Let's take a step back; what does it mean for a Context to be generic? Some component Producer<T> that represents the Producer half of a Context would presumably only provide values of type T, right?
Now consider the following:
<Producer<string> value="123">
<Producer<number> value={123}>
<Consumer />
</Producer>
</Producer>
How SHOULD this behave? Which value should the consumer get?
If Producer<number> overrides Producer<string> (i.e., consumer gets 123), the generic type doesn't do anything. Calling it number at the Producer level doesn't enforce that you'll get a number when consuming, so specifying it there is false hope.
If both Producers are meant to be completely separate (i.e., consumer gets "123"), they must come from two separate Contexts instances that are specific to the type they hold. But then they're not generic!
In either case, there's no value in passing a type directly to Producer. That's not to say generics are useless when Context is at play...
How CAN I make a generic list component?
As someone who has been using generic components for a little while, I don't think your list example is overly abstract. It's just that you can't enforce type agreement between Producer and Consumer - just like you can't "enforce" the type of a value you get from a web request, or from local storage, or third-party code!
Ultimately, this means using something like any when defining the Context and specifying an expected type when consuming that Context.
Example
const listContext = React.createContext<ListProps<any>>({ onSelectionChange: () => {} });
interface ListProps<TItem> {
onSelectionChange: (selected: TItem | undefined) => void;
}
// Note that List is still generic!
class List<TItem> extends React.Component<ListProps<TItem>> {
public render() {
return (
<listContext.Provider value={this.props}>
{this.props.children}
</listContext.Provider>
);
}
}
interface CustomListItemProps<TItem> {
item: TItem;
}
class CustomListItem<TItem> extends React.Component<CustomListItemProps<TItem>> {
public render() {
// Get the context value and store it as ListProps<TItem>.
// Then build a list item that can call onSelectionChange based on this.props.item!
}
}
interface ContactListProps {
contacts: Contact[];
}
class ContactList extends React.Component<ContactListProps> {
public render() {
return (
<List<Contact> onSelectionChange={console.log}>
{contacts.map(contact => <ContactListItem contact={contact} />)}
</List>
);
}
}
Check how Formik dose it
For me it was important to check types when using context (useContext) just like Formik useFormikContext.
Example of generic type
type YourContextType<T> = { state: T; setState: (s: T) => void }
Create context (do not care about type)
const YourContext = createContext<YourContextType<any>>(
undefined as any,
);
Context hook with generic type
function useYourContext<T>() {
const context = useContext<YourContextType<T>>(YourContext);
if (context === undefined) {
// assert if context is available
throw new Error('No context provided');
}
return context;
}
Use hook
const { state, changeState } = useYourContext<{ name: string }>();
You will see state and changeState have types. Maybe it is not in 100% what you are looking for but for me it was enough. Check #TheRubberDuck explanation
src: https://github.com/jaredpalmer/formik/blob/master/packages/formik/src/FormikContext.tsx

Material UI's withTheme() hides component from ref prop

For the rare times when you need a reference to another JSX element in React, you can use the ref prop, like this:
class Widget extends React.PureComponent {
example() {
// do something
}
render() {
...
<Widget ref={r => this.mywidget = r}/>
<OtherWidget onClick={e => this.mywidget.example()}/>
Here, the Widget instance is stored in this.mywidget for later use, and the example() function can be called on it.
In Material UI, you can wrap components around a withTheme() call to make the theme accessible in their props:
export default withTheme()(Widget);
However if this is done, the ref receives an instance of WithTheme rather than Widget. This means the example() function is no longer accessible.
Is there some way to use ref with a component wrapped by withTheme() so that the underlying object can still be accessed, in the same manner as if withTheme() had not been used?
Here is an example demonstrating the issue. Lines 27 and 28 can be commented/uncommented to see that things only fail when the withTheme() call is added.
In order to get the ref of the component which is wrapped with withStyles, you can create a wrapper around Widget, and use that with withStyles like
const WithRefWidget = ({ innerRef, ...rest }) => {
console.log(innerRef);
return <Widget ref={innerRef} {...rest} />;
};
const MyWidget = withTheme()(WithRefWidget);
class Demo extends React.PureComponent {
constructor(props) {
super(props);
this.mywidget = null;
}
render() {
return (
<React.Fragment>
<MyWidget
innerRef={r => {
console.log(r);
this.mywidget = r;
}}
/>
<Button
onClick={e => {
e.preventDefault();
console.log(this.mywidget);
}}
variant="raised"
>
Click
</Button>
</React.Fragment>
);
}
}
Have a look at this answer to see an other alternative approach
losing functions when using recompose component as ref
This is a shorter alternative based on Shubham Khatri's answer. That answer works when you can't alter the inner component, this example is a bit shorter when you can modify the inner component.
Essentially ref doesn't get passed through withTheme() so you have to use a prop with a different name, and implement ref functionality on it yourself:
class Widget extends React.PureComponent {
constructor(props) {
props.ref2(this); // duplicate 'ref' functionality for the 'ref2' prop
...
const MyWidget = withTheme()(Widget);
...
<MyWidget
ref2={r => {
console.log(r);
this.mywidget = r;
}}
/>

is there any way to access the parent component instance in React?

I know it's not a functional approach to be able to do something like this.parent in a React component, and I can't seem to find any properties on a React component instance that lead to the parent, but I'm just looking to be able to do some custom things where I need this.
Before anyone wastes their time explaining it's not the functional React "way," understand that I need this because of the following I'm trying to achieve:
Build a transpiler for Meteor's Spacebars templating engine, whose rendering model does take into consideration parent components/templates.
I've already built a transpiler that modifies the output jsx to achieve this. I do this by passing in parent={this} in all child components composed. However, after the fact it occurred to me that maybe I simply don't know of something that will give me a way to access the parent component instance without additional transpilation modifications.
Any tips would be much appreciated.
There's nothing wrong if you need to access the parent's props and functions from the children.
The point is that you should never use React internals and undocumented APIs.
First of all, they are likely to change (breaking your code) and, most importantly, there are many other approaches which are cleaner.
Passing props to children
class Parent extends React.Component {
constructor(props) {
super(props)
this.fn = this.fn.bind(this)
}
fn() {
console.log('parent')
}
render() {
return <Child fn={this.fn} />
}
}
const Child = ({ fn }) => <button onClick={fn}>Click me!</button>
Working example
Using context (if there's no direct parent/child relation)
class Parent extends React.Component {
constructor(props) {
super(props)
this.fn = this.fn.bind(this)
}
getChildContext() {
return {
fn: this.fn,
}
}
fn() {
console.log('parent')
}
render() {
return <Child fn={this.fn} />
}
}
Parent.childContextTypes = {
fn: React.PropTypes.func,
}
const Child = (props, { fn }) => <button onClick={fn}>Click me!</button>
Child.contextTypes = {
fn: React.PropTypes.func,
}
Working example
Update for React 0.13 and newer
Component._owner was deprecated in React 0.13, and _currentElement no longer exists as a key in this._reactInternalInstance. Therefore, using the solution below throws Uncaught TypeError: Cannot read property '_owner' of undefined.
The alternative is, as of React 16, this._reactInternalFiber._debugOwner.stateNode.
You've already recognized that this is not a good thing to do almost always, but I'm repeating it here for people that don't read the question very well: this is generally an improper way to get things done in React.
There's nothing in the public API that will allow you to get what you want. You may be able to get to this using the React internals, but because it's a private API it's liable to break at any time.
I repeat: you should almost certainly not use this in any sort of production code.
That said, you can get the internal instance of the current component using this. _reactInternalInstance. In there, you can get access to the element via the _currentElement property, and then the owner instance via _owner._instance.
Here's an example:
var Parent = React.createClass({
render() {
return <Child v="test" />;
},
doAThing() {
console.log("I'm the parent, doing a thing.", this.props.testing);
}
});
var Child = React.createClass({
render() {
return <button onClick={this.onClick}>{this.props.v}</button>
},
onClick() {
var parent = this._reactInternalInstance._currentElement._owner._instance;
console.log("parent:", parent);
parent.doAThing();
}
});
ReactDOM.render(<Parent testing={true} />, container);
And here's a working JSFiddle example: http://jsfiddle.net/BinaryMuse/j8uaq85e/
Tested with React 16
I was playing around with something similar using context, tho to anyone reading this, for most usual cases, accessing the parent is not advised!
I created a holder that when used, would always have a reference to the first holder up the display list, so its 'parent' if you will. Looked something like this:
const ParentContext = React.createContext(null);
// function to apply to your react component class
export default function createParentTracker(componentClass){
class Holder extends React.PureComponent {
refToInstance
render(){
return(
<ParentContext.Consumer>
{parent => {
console.log('I am:', this, ' my parent is:',parent ? parent.name : 'null');
return(
<ParentContext.Provider value={this}>
<componentClass ref={inst=>refToInstance=inst} parent={parent} {...this.props} />
</ParentContext.Provider>
)}
}
</ ParentContext.Consumer>
)
}
}
// return wrapped component to be exported in place of yours
return Holder;
}
Then to use it you would pass your react component to the method when you export it like so:
class MyComponent extends React.Component {
_doSomethingWithParent(){
console.log(this.props.parent); // holder
console.log(this.props.parent.refToInstance); // component
}
}
// export wrapped component instead of your own
export default createParentTracker(MyComponent);
This way any component exporting the function will get its parent's holder passed in as a prop (or null if nothing is further up the hierarchy). From there you can grab the refToInstance. It will be undefined until everything is mounted though.

Resources