React typescript render props

React Render Props in TypeScript

As with previous articles, this article also requires you to have some background in render props, if not Official Documents It may be of great help to you.This article will use functions as render props mode for children and context API combined with React as examples.If you want to use render props like render, just use children from the example below as the props you want to render.

To show render props, we’ll rewrite the previous article makeCounter HOC .Let’s start with the HOC version:

export interface InjectedCounterProps < value: number; onIncrement(): void; onDecrement(): void; >interface MakeCounterProps < minValue?: number; maxValue?: number; >interface MakeCounterState < value: number; >const makeCounter = ( Component: React.ComponentType ) => class MakeCounter extends React.Component < Subtract& MakeCounterProps, MakeCounterState > < state: MakeCounterState = < value: 0, >; increment = () => < this.setState(prevState =>(< value: prevState.value === this.props.maxValue ? prevState.value : prevState.value + 1, >)); >; decrement = () => < this.setState(prevState =>(< value: prevState.value === this.props.minValue ? prevState.value : prevState.value - 1, >)); >; render() < const < minValue, maxValue, . props >= this.props; return ( value= onIncrement= onDecrement= /> ); > >;

HOC injects value and two callback functions (onIncrement and onDecrement) into the component, and uses two props, minValue and maxValue, inside the HOC without passing them to the component.We discussed how not to pass props can be problematic if the component needs to know these values, and if multiple HOC wrapping components are used, the naming of injected props may conflict with that of other HOC-injected props.

Читайте также:  Изменение регистра строк javascript

makeCounter HOC will be rewritten as follows:

interface InjectedCounterProps < value: number; onIncrement(): void; onDecrement(): void; >interface MakeCounterProps < minValue?: number; maxValue?: number; children(props: InjectedCounterProps): JSX.Element; >interface MakeCounterState < value: number; >class MakeCounter extends React.Component < state: MakeCounterState = < value: 0, >; increment = () => < this.setState(prevState =>(< value: prevState.value === this.props.maxValue ? prevState.value : prevState.value + 1, >)); >; decrement = () => < this.setState(prevState =>(< value: prevState.value === this.props.minValue ? prevState.value : prevState.value - 1, >)); >; render() < return this.props.children(< value: this.state.value, onIncrement: this.increment, onDecrement: this.decrement, >); > >

Here are some changes that need to be noted.First, injectedCounterProps are retained because we need to define an interface t for props on the render props function call instead of props passed to the component (as with HOC).MakeCounter (MakeCounterProps) props have changed, with the following added:

children(props: InjectedCounterProps): JSX.Element;

This is render prop, and then you need a props injected with the function strip inside the component and return the JSX element.Here is an example of how it highlights this point:

interface CounterProps < style: React.CSSProperties; minValue?: number; maxValue?: number; >const Counter = (props: CounterProps) => ( maxValue=> ( 
>
)>
);

MakeCounter’s own component declaration is much simpler; it is no longer wrapped in a function because it is no longer temporary and the input is simpler, requiring no intersection of generics, differences, and types.It has only simple MakeCounterProps and MakeCounterState, just like any other component:

class MakeCounter extends React.Component

Finally, render () does less work; it’s just a function call with an injected props-the props extension operator that doesn’t need to be destroyed and objects expanded!

The render prop component then allows more control over the naming and flexibility of props, which is equivalent to HOC:

interface CounterProps < style: React.CSSProperties; value: number; minCounterValue?: number; maxCounterValue?: number; >const Counter = (props: CounterProps) => ( maxValue= > ( 
Some other value:
>
Min value: ) : null> Max value: ) : null>
)> );

With all these benefits, especially simpler inputs, why not keep using render props?Of course, there are no problems with this, but be aware of some issues with the render props component.

First, there’s a problem outside of focus; the MakeCounter component is now placed inside the Counter component instead of wrapping it, making it more difficult to isolate the two components for testing.Second, since props are injected into the rendering function of the component, they cannot be used in lifecycle methods (provided the counter is changed to a class component).

These two issues are easy to solve because you can simply generate a new component using the render props component:

interface CounterProps extends InjectedCounterProps < style: React.CSSProperties; >const Counter = (props: CounterProps) => ( 
>
); interface WrappedCounterProps extends CounterProps < minValue?: number; maxValue?: number; >const WrappedCounter = (< minValue, maxValue, . props >: WrappedCounterProps) => ( maxValue=> />> );

Another problem is that, in general, it is not very convenient and users now need to write many template files, especially if they only want to wrap components in a separate temporary file and use props as is.This can be remedied by generating HOC s from render props components:

import < Subtract, Omit >from 'utility-types'; import MakeCounter, < MakeCounterProps, InjectedCounterProps >from './MakeCounter'; type MakeCounterHocProps = Omit; const makeCounter = ( Component: React.ComponentType ): React.SFC & MakeCounterHocProps> => (< minValue, maxValue, . props >: MakeCounterHocProps) => ( maxValue=>   />> );

Here, the techniques in the previous article, along with the existing types of render props components, are used to generate HOCs.The only thing to note here is that we have to remove render prop(children) from the props of HOC in order not to expose it in use:

type MakeCounterHocProps = Omit;

Finally, the trade-off between HOC and render props components boils down to flexibility and convenience.This can be solved by writing the render props component first, then generating a HOC from it, which allows the user to choose between the two.This approach is becoming more common in reusable component libraries, such as the excellent render-fns library.

As far as TypeScript is concerned, it is undoubtedly much more difficult to define the type of hocs; although the examples in these two articles show that this burden is borne by the HOC provider, not the user.In terms of usage, you can think that using HOC is easier than using render props components.

Prior to react v16.8.0, I recommended using the render props component to improve typing flexibility and simplicity. If necessary, such as building a reusable component library, or for render props components that are simply used in a project, I will only generate HOC from them.After releasing the react hook s in react v16.8.0, I strongly recommend using them on two higher-order components or render props where possible, since their types are simpler.

Posted by echo10 on Wed, 08 May 2019 23:54:38 -0700

Hot Keywords

  • Java — 6961
  • Database — 2683
  • Python — 2616
  • Programming — 2419
  • Attribute — 2418
  • Javascript — 2383
  • Spring — 2111
  • Android — 2077
  • xml — 1972
  • Linux — 1818
  • JSON — 1764
  • network — 1748
  • github — 1720
  • less — 1676
  • MySQL — 1538
  • SQL — 1332
  • PHP — 1318
  • encoding — 1179
  • Mobile — 1029
  • Apache — 925

Источник

TypeScript and React: Render props and child render props

Stefan Baumgartner

Render props is, according to the offical docs, a technique for sharing code between React components using a prop whose value is a function. The idea is to make components composable but being flexible in what to share.

Render props #

Let’s look at the example from the official docs and make it type-safe. Turns out, this is quite easy.

We want to have a Cat that chases a Mouse. It chases our computer’s mouse, in that case.

The Cat renders an image of cat that is positioned based on mouse coordinates. For this we need the Cat s props, which points to a MouseState . MouseState holds x and y coordinates for us.s

import React,  FC, Component, MouseEvent > from 'react';

type CatProps =
mouse: MouseState
>

type MouseState =
x: number,
y: number
>


export class Cat extends ComponentCatProps>
render()
const mouse > = this.props;
return (
img src="cat.gif" style= position: 'absolute', left: mouse.x, top: mouse.y > > />
);
>
>

That’s the Cat , let’s look at the Mouse .

The props from Mouse are pretty straight forward. We have one property – render (hence the name) – which accepts a MouseState . MouseState becomes the glue between Cat and Mouse

type MouseProps =  
render(state: MouseState): void
>

The Mouse component itself builds on MouseProps and MouseState . Note that we call the render function instead of merely adding children.

export class Mouse extends ComponentMouseProps, MouseState>  
constructor(props: MouseProps)
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = x: 0, y: 0 >;
>

handleMouseMove(event: MouseEvent)
this.setState(
x: event.clientX,
y: event.clientY
>);
>

render()
return (
div
style= height: '100vh', width: '100vw' > >
onMouseMove=this.handleMouseMove>>
/*
Instead of providing a static representation of what renders,
use the `render` prop to dynamically determine what to render.
*/
>
this.props.render(this.state)>
/div>
);
>
>

Now let’s combine everything. The Mouse component has one prop, render , and the function returns the hunting Cat . Here we don’t have any extra TypeScript annotations. But TypeScript warns us if the function properties from mouse are incompatible with the mouse prop from Cat :

export const MouseTracker = () => div> 
h1>Move the mouse around!/h1>
Mouse render=mouse => (
Cat mouse=mouse> />
)>/>
/div>;

Children render props #

Turns out that children can be a function as well. As for typings, the same typings apply. We only have to change a little bit of code.

export class Mouse extends Component   render() return (  style= < < height: '100vh', width: '100vw' >> onMouseMove=> Instead of providing a static representation of what renders, use the `render` prop to dynamically determine what to render. */> - +   ); > >

export const MouseTracker = () =>

Move the mouse around!

- ( - /> - )>/> + + ( + /> + )> + /div>;

Context #

One concept that relies heavily on render props is Context. I explain context in its own chapter.

Bottom line #

With typings you can easily see that state is the key for render props. We pass state from one component to the other through a function. This also means that state is the one type that gets shared between components. Again, there’s some excellent reading by Ali Sharif

Источник

React Render Props with TypeScript

React render props are a useful tool for sharing logic across multiple components. Think of them as a special case of higher-order component where–instead of creating new child components–different render methods can be injected.

Suppose we have a logical component (call it CounterDataProvider ) responsible for managing a count value consumed by components deeper inside the React DOM. If we want to allow different rendering strategies for the count, we can type CounterDataProvider to receive a render prop.

type CounterState = < count: number, >; type ChildProps = CounterState & < increment(d: number): void, >; type CounterProps = < render(state: ChildProps): React.ReactNode, >; 

Yes, it’s exactly what it sounds like. The props we’ll pass to CounterDataProvider ‘s props include a render method that takes in ChildProps and produces a ReactNode –the exact same signature we’d see in a garden-variety Stateless Functional Component (SFC). And that’s exactly what we’ll pass to render !

import * as React from 'react'; class CounterDataProvider extends React.Component < readonly state: CounterState = < count: 44, >; increment = (delta: number) => this.setState(< count: this.state.count + delta >) render() < return this.props.render(< . this.state, increment: this.increment, >); > > export default () => ( ( 
Count:
)> /> );

If we wanted to imagine a completely different UI for the counter, no problem! Just give render a different SFC and away you go.

Find a demo of it all working together in the React with TypeScript repository on Github.

Join the newsletter

Infrequent updates on software, teams, & tech.

Источник

Оцените статью