Tag: Render

Render Snarky Comments in Comic Sans

Hilarious idea by Zach Leatherman. To test if a comment is “snarky” or not, there is an npm package up to the task.

On this site, we generally just delete snarky comments, but I still run a WordPress plugin that allows me to “feature” or “bury” comments. It’s old but it still works fine in the latest WordPress. We use the bury functionality for comments that aren’t rude, snarky, or spam, but also don’t add much to the post. We try to downplay those so they aren’t wasting people’s time scrolling through the threads.

I dig the Comic Sans idea, so I’m gonna give it a shot. 😉

Direct Link to ArticlePermalink

The post Render Snarky Comments in Comic Sans appeared first on CSS-Tricks.

CSS-Tricks

, , , ,

A Practical Use Case for Vue Render Functions: Building a Design System Typography Grid

This post covers how I built a typography grid for a design system using Vue render functions. Here’s the demo and the code. I used render functions because they allow you to create HTML with a greater level of control than regular Vue templates, yet surprisingly I couldn’t find very much when I web searched around for real-life, non-tutorial applications of them. I’m hoping this post will fill that void and provide a helpful and practical use case on using Vue render functions.

I’ve always found render functions to be a little out-of-character for Vue. While the rest of the framework emphasizes simplicity and separation of concerns, render functions are a strange and often difficult-to-read mix of HTML and JavaScript.

For example, to display:

<div class="container">   <p class="my-awesome-class">Some cool text</p> </div>

…you need:

render(createElement) {   return createElement("div", { class: "container" }, [     createElement("p", { class: "my-awesome-class" }, "Some cool text")   ]) }

I suspect that this syntax turns some people off, since ease-of-use is a key reason to reach for Vue in the first place. This is a shame because render functions and functional components are capable of some pretty cool, powerful stuff. In the spirit of demonstrating their value, here’s how they solved an actual business problem for me.

Quick disclaimer: It will be super helpful to have the demo open in another tab to reference throughout this post.

Defining criteria for a design system

My team wanted to include a page in our VuePress-powered design system showcasing different typography options. This is part of a mockup that I got from our designer.

A screenshot of the typographic design system. There are four columns where the first shows the style name with the rendered style, second is the element or class, third shows the properties that make the styles, and fourth is the defined usage.

And here’s a sample of some of the corresponding CSS:

h1, h2, h3, h4, h5, h6 {   font-family: "balboa", sans-serif;   font-weight: 300;   margin: 0; }  h4 {   font-size: calc(1rem - 2px); }  .body-text {   font-family: "proxima-nova", sans-serif; }  .body-text--lg {   font-size: calc(1rem + 4px); }  .body-text--md {   font-size: 1rem; }  .body-text--bold {   font-weight: 700; }  .body-text--semibold {   font-weight: 600; }

Headings are targeted with tag names. Other items use class names, and there are separate classes for weight and size.

Before writing any code, I created some ground rules:

  • Since this is really a data visualization, the data should be stored in a separate file.
  • Headings should use semantic heading tags (e.g. <h1>, <h2>, etc.) instead of having to rely on a class.
  • Body content should use paragraph (<p>) tags with the class name (e.g. <p class="body-text--lg">).
  • Content types that have variations should be grouped together by wrapping them in the root paragraph tag, or corresponding root element, without a styling class. Children should be wrapped with <span> and the class name.
<p>   <span class="body-text--lg">Thing 1</span>   <span class="body-text--lg">Thing 2</span> </p>
  • Any content that’s not demonstrating special styling should use a paragraph tag with the correct class name and <span> for any child nodes.
<p class="body-text--semibold">   <span>Thing 1</span>   <span>Thing 2</span> </p>
  • Class names should only need to be written once for each cell that’s demonstrating styling.

Why render functions make sense

I considered a few options before starting:

Hardcoding

I like hardcoding when appropriate, but writing my HTML by hand would have meant typing out different combinations of the markup, which seemed unpleasant and repetitive. It also meant that data couldn’t be kept in a separate file, so I ruled out this approach.

Here’s what I mean:

<div class="row">   <h1>Heading 1</h1>   <p class="body-text body-text--md body-text--semibold">h1</p>   <p class="body-text body-text--md body-text--semibold">Balboa Light, 30px</p>   <p class="group body-text body-text--md body-text--semibold">     <span>Product title (once on a page)</span>     <span>Illustration headline</span>   </p> </div>

Using a traditional Vue template

This would normally be the go-to option. However, consider the following:

See the Pen
Different Styles Example
by Salomone Baquis (@soluhmin)
on CodePen.

In the first column, we have:

– An <h1>> tag rendered as-is.
– A <p> tag that groups some <span> children with text, each with a class (but no special class on the <p> tag).
– A <p> tag with a class and no children.

The result would have meant many instances of v-if and v-if-else, which I knew would get confusing fast. I also disliked all of that conditional logic inside the markup.

Because of these reasons, I chose render functions. Render functions use JavaScript to conditionally create child nodes based on all of the criteria that’s been defined, which seemed perfect for this situation.

Data model

As I mentioned earlier, I’d like to keep typography data in a separate JSON file so I can easily make changes later without touching markup. Here’s the raw data.

Each object in the file represents a different row.

{   "text": "Heading 1",   "element": "h1", // Root wrapping element.   "properties": "Balboa Light, 30px", // Third column text.   "usage": ["Product title (once on a page)", "Illustration headline"] // Fourth column text. Each item is a child node.  }

The object above renders the following HTML:

<div class="row">   <h1>Heading 1</h1>   <p class="body-text body-text--md body-text--semibold">h1</p>   <p class="body-text body-text--md body-text--semibold">Balboa Light, 30px</p>   <p class="group body-text body-text--md body-text--semibold">     <span>Product title (once on a page)</span>     <span>Illustration headline</span>   </p> </div>

Let’s look at a more involved example. Arrays represent groups of children. A classes object can store classes. The base property contains classes that are common to every node in the cell grouping. Each class in variants is applied to a different item in the grouping.

{   "text": "Body Text - Large",   "element": "p",   "classes": {     "base": "body-text body-text--lg", // Applied to every child node     "variants": ["body-text--bold", "body-text--regular"] // Looped through, one class applied to each example. Each item in the array is its own node.    },   "properties": "Proxima Nova Bold and Regular, 20px",   "usage": ["Large button title", "Form label", "Large modal text"] }

Here’s how that renders:

<div class="row">   <!-- Column 1 -->   <p class="group">     <span class="body-text body-text--lg body-text--bold">Body Text - Large</span>     <span class="body-text body-text--lg body-text--regular">Body Text - Large</span>   </p>   <!-- Column 2 -->   <p class="group body-text body-text--md body-text--semibold">     <span>body-text body-text--lg body-text--bold</span>     <span>body-text body-text--lg body-text--regular</span>   </p>   <!-- Column 3 -->   <p class="body-text body-text--md body-text--semibold">Proxima Nova Bold and Regular, 20px</p>   <!-- Column 4 -->   <p class="group body-text body-text--md body-text--semibold">     <span>Large button title</span>     <span>Form label</span>     <span>Large modal text</span>   </p> </div>

The basic setup

We have a parent component, TypographyTable.vue, which contains the markup for the wrapper table element, and a child component, TypographyRow.vue, which creates a row and contains our render function.

I loop through the row component, passing the row data as props.

<template>   <section>     <!-- Headers hardcoded for simplicity -->     <div class="row">       <p class="body-text body-text--lg-bold heading">Hierarchy</p>       <p class="body-text body-text--lg-bold heading">Element/Class</p>       <p class="body-text body-text--lg-bold heading">Properties</p>       <p class="body-text body-text--lg-bold heading">Usage</p>     </div>       <!-- Loop and pass our data as props to each row -->     <typography-row       v-for="(rowData, index) in $ options.typographyData"       :key="index"       :row-data="rowData"     />   </section> </template> <script> import TypographyData from "@/data/typography.json"; import TypographyRow from "./TypographyRow"; export default {   // Our data is static so we don't need to make it reactive   typographyData: TypographyData,   name: "TypographyTable",   components: {     TypographyRow   } }; </script>

One neat thing to point out: the typography data can be a property on the Vue instance and be accessed using $ options.typographyData since it doesn’t change and doesn’t need to be reactive. (Hat tip to Anton Kosykh.)

Making a functional component

The TypographyRow component that passes data is a functional component. Functional components are stateless and instanceless, which means that they have no this and don’t have access to any Vue lifecycle methods.

The empty starting component looks like this:

// No <template> <script> export default {   name: "TypographyRow",   functional: true, // This property makes the component functional   props: {     rowData: { // A prop with row data       type: Object     }   },   render(createElement, { props }) {     // Markup gets rendered here   } } </script>

The render method takes a context argument, which has a props property that’s de-structured and used as the second argument.

The first argument is createElement, which is a function that tells Vue what nodes to create. For brevity and convention, I’ll be abbreviating createElement as h. You can read about why I do that in Sarah’s post.

h takes three arguments:

  1. An HTML tag (e.g. div)
  2. A data object with template attributes (e.g. { class: 'something'})
  3. Text strings (if we’re just adding text) or child nodes built using h
render(h, { props }) {   return h("div", { class: "example-class }, "Here's my example text") }

OK, so to recap where we are at this point, we’ve covered creating:

  • a file with the data that’s going to be used in my visualization;
  • a regular Vue component where I’m importing the full data file; and
  • the beginning of a functional component that will display each row.

To create each row, the data from the JSON file needs to be passed into arguments for h. This could be done all at once, but that involves a lot of conditional logic and is confusing.

Instead, I decided to do it in two parts:

  1. Transform the data into a predictable format.
  2. Render the transformed data.

Transforming the common data

I wanted my data in a format that would match the arguments for h, but before doing this, I wrote out how I wanted things structured:

// One cell {   tag: "", // HTML tag of current level   cellClass: "", // Class of current level, null if no class exists for that level   text: "", // Text to be displayed    children: [] // Children each follow this data model, empty array if no child nodes }

Each object represents one cell, with four cells making up each row (an array).

// One row [ { cell1 }, { cell2 }, { cell3 }, { cell4 } ]

The entry point would be a function like:

function createRow(data) { // Pass in the full row data and construct each cell   let { text, element, classes = null, properties, usage } = data;   let row = [];   row[0] = createCellData(data) // Transform our data using some shared function   row[1] = createCellData(data)   row[2] = createCellData(data)   row[3] = createCellData(data)    return row; }

Let’s take another look at our mockup.

The first column has styling variations, but the rest seem to follow the same pattern, so let’s start with those.

Again, the desired model for each cell is:

{   tag: "",   cellClass: "",    text: "",    children: [] }

This gives us a tree-like structure for each cell since some cells have groups of children. Let’s use two functions to create the cells.

  • createNode takes each of our desired properties as arguments.
  • createCell wraps around createNode so that we can check if the text that we’re passing in is an array. If it is, we build up an array of child nodes.
// Model for each cell function createCellData(tag, text) {   let children;   // Base classes that get applied to every root cell tag   const nodeClass = "body-text body-text--md body-text--semibold";   // If the text that we're passing in as an array, create child elements that are wrapped in spans.    if (Array.isArray(text)) {     children = text.map(child => createNode("span", null, child, children));   }   return createNode(tag, nodeClass, text, children); } // Model for each node function createNode(tag, nodeClass, text, children = []) {   return {     tag: tag,     cellClass: nodeClass,     text: children.length ? null : text,     children: children   }; }

Now, we can do something like:

function createRow(data) {   let { text, element, classes = null, properties, usage } = data;   let row = [];   row[0] = ""   row[1] = createCellData("p", ?????) // Need to pass in class names as text    row[2] = createCellData("p", properties) // Third column   row[3] = createCellData("p", usage) // Fourth column    return row; }

We pass properties and usage to the third and fourth columns as text arguments. However, the second column is a little different; there, we’re displaying the class names, which are stored in the data file like:

"classes": {   "base": "body-text body-text--lg",   "variants": ["body-text--bold", "body-text--regular"] },

Additionally, remember that headings don’t have classes, so we want to show the heading tag names for those rows (e.g. h1, h2, etc.).

Let’s create some helper functions to parse this data into a format that we can use for our text argument.

// Pass in the base tag and class names as arguments function displayClasses(element, classes) {   // If there are no classes, return the base tag (appropriate for headings)   return getClasses(classes) ? getClasses(classes) : element; }  // Return the node class as a string (if there's one class), an array (if there are multiple classes), or null (if there are none.)  // Ex. "body-text body-text--sm" or ["body-text body-text--sm body-text--bold", "body-text body-text--sm body-text--italic"] function getClasses(classes) {   if (classes) {     const { base, variants = null } = classes;     if (variants) {       // Concatenate each variant with the base classes       return variants.map(variant => base.concat(`$ {variant}`));     }     return base;   }   return classes; }

Now we can do this:

function createRow(data) {   let { text, element, classes = null, properties, usage } = data;   let row = [];   row[0] = ""   row[1] = createCellData("p", displayClasses(element, classes)) // Second column   row[2] = createCellData("p", properties) // Third column   row[3] = createCellData("p", usage) // Fourth column    return row; }

Transforming the demo data

This leaves the first column that demonstrates the styles. This column is different because we’re applying new tags and classes to each cell instead of using the class combination used by the rest of the columns:

<p class="body-text body-text--md body-text--semibold">

Rather than try to do this in createCellData or createNodeData, let’s make another function to sit on top of these base transformation functions and handle some of the new logic.

function createDemoCellData(data) {   let children;   const classes = getClasses(data.classes);   // In cases where we're showing off multiple classes, we need to create children and apply each class to each child.   if (Array.isArray(classes)) {     children = classes.map(child =>       // We can use "data.text" since each node in a cell grouping has the same text       createNode("span", child, data.text, children)     );   }   // Handle cases where we only have one class   if (typeof classes === "string") {     return createNode("p", classes, data.text, children);   }   // Handle cases where we have no classes (ie. headings)   return createNode(data.element, null, data.text, children); }

Now we have the row data in a normalized format that we can pass to our render function:

function createRow(data) {   let { text, element, classes = null, properties, usage } = data   let row = []   row[0] = createDemoCellData(data)   row[1] = createCellData("p", displayClasses(element, classes))   row[2] = createCellData("p", properties)   row[3] = createCellData("p", usage)    return row }

Rendering the data

Here’s how we actually render the data to display:

// Access our data in the "props" object const rowData = props.rowData;  // Pass it into our entry transformation function const row = createRow(rowData);  // Create a root "div" node and handle each cell return h("div", { class: "row" }, row.map(cell => renderCells(cell)));  // Traverse cell values function renderCells(data) {    // Handle cells with multiple child nodes   if (data.children.length) {     return renderCell(       data.tag, // Use the base cell tag       { // Attributes in here         class: {           group: true, // Add a class of "group" since there are multiple nodes           [data.cellClass]: data.cellClass // If the cell class isn't null, apply it to the node         }       },       // The node content       data.children.map(child => {         return renderCell(           child.tag,           { class: child.cellClass },           child.text         );       })     );   }    // If there are no children, render the base cell   return renderCell(data.tag, { class: data.cellClass }, data.text); }  // A wrapper function around "h" to improve readability function renderCell(tag, classArgs, text) {   return h(tag, classArgs, text); }

And we get our final product! Here’s the source code again.

Wrapping up

It’s worth pointing out that this approach represents an experimental way of addressing a relatively trivial problem. I’m sure many people will argue that this solution is needlessly complicated and over-engineered. I’d probably agree.

Despite the up-front cost, however, the data is now fully separated from the presentation. Now, if my design team adds or removes rows, I don’t have to dig into messy HTML — I just update a couple of properties in the JSON file.

Is it worth it? Like everything else in programming, I guess it depends. I will say that this comic strip was in the back of my mind as I worked on this:

A three-panel comic strip. First panel is a stick figure at a dinner table asking to pass the salt. Second panel is the same figure with no dialogue. Third panel is another figure saying he's building a system to pass the condiments and that it will save time in the long run. First figure says it's already been 20 minutes.
Source: https://xkcd.com/974

Maybe that’s an answer. I’d love to hear all of your (constructive) thoughts and suggestions, or if you’ve tried other ways of going about a similar task.

The post A Practical Use Case for Vue Render Functions: Building a Design System Typography Grid appeared first on CSS-Tricks.

CSS-Tricks

, , , , , , , ,
[Top]

Using React Portals to Render Children Outside the DOM Hierarchy

Say we need to render a child element into a React application. Easy right? That child is mounted to the nearest DOM element and rendered inside of it as a result.

render() {   return (     <div>       // Child to render inside of the div     </div>   ); }

But! What if we want to render that child outside of the div somewhere else? That could be tricky because it breaks the convention that a component needs to render as a new element and follow a parent-child hierarchy. The parent wants to go where its child goes.

That’s where React Portals come in. They provide a way to render elements outside the DOM hierarchy so that elements are a little more portable. It may not be a perfect analogy, but Portals are sort of like the pipes in Mario Bros. that transport you from the normal flow of the game and into a different region.

The cool thing about Portals? Even though they trigger their own events that are independent of the child’s parent element, the parent is still listening to those events, which can be useful for passing events across an app.

We’re going to create a Portal together in this post then make it into a re-usable component. Let’s go!

The example we’re building

Here’s a relatively simple example of a Portal in action:

See the Pen React Portal by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

Toggling an element’s visibility is nothing new. But, if you look at the code carefully, you’ll notice that the outputted element is controlled by the button even though it is not a direct descendent of it. In fact, if you compare the source code to the rendered output in DevTools, you’ll see the relationship:

So the outputted element’s parent actually listens for the button click event and allows the child to be inserted even though it and the button are separate siblings in the DOM. Let’s break down the steps for creating this toggled Portal element to see how it all works.

Step 1: Create the Portal element

The first line of a React application will tell you that an App element is rendered on the document root using ReactDOM. Like this;

ReactDOM.render(<App />, document.getElementById("root"));

We need to place the App element in an HTML file to execute it:

<div id="App"></div>

Same sort of thing with Portals. First thing to creating a Portal is to create a new div element in the HTML file.

<div id="portal"></div>

This div will serve as our target. We’re using #portal as the ID, but it doesn’t have to be that. Any component that gets rendered inside this target div will maintain React’s context. We need to store the div as the value of a variable so we can make use of the Portal component that we’ll create:

const portalRoot = document.getElementById("portal");

Looks a lot like the method to execute the App element, right?

Step 2: Create a Portal component

Next, let’s set up the Portal as a component:

class Portal extends React.Component {   constructor() {     super();     // 1: Create a new div that wraps the component     this.el = document.createElement("div");   }   // 2: Append the element to the DOM when it mounts   componentDidMount = () => {     portalRoot.appendChild(this.el);   };   // 3: Remove the element when it unmounts   componentWillUnmount = () => {     portalRoot.removeChild(this.el);   };   render() {     // 4: Render the element's children in a Portal     const { children } = this.props;     return ReactDOM.createPortal(children, this.el);   } }

Let’s step back and take a look at what is happening here.

We create a new div element in the constructor and set it as a value to this.el. When the Portal component mounts, this.el is appended as a child to that div in the HTML file where we added it. That’s the <div id="portal"></div> line in our case.

The DOM tree will look like this.

<div> // Portal, which is also portalRoot   <div> // this.el   </div> </div>

If you’re new to React and are confused by the concept of mounting and unmounting an element, Jake Trent has a good explanation. TL;DR: Mounting is the moment the element is inserted into the DOM.

When the component unmounts we want to remove the child to avoid any memory leakage. We will import this Portal component into another component where it gets used, which is the the div that contains the header and button in our example. In doing so, we’ll pass the children elements of the Portal component along with it. This is why we have this.props.children.

Step 3: Using the Portal

To render the Portal component’s children, we make use of ReactDOM.createPortal(). This is a special ReactDOM method that accepts the children and the element we created. To see how the Portal works, let’s make use of it in our App component.

But, before we do that, let’s cover the basics of how we want the App to function. When the App loads, we want to display a text and a button — we can then toggle the button to either show or hide the Portal component.

class App extends React.Component {   // The initial toggle state is false so the Portal element is out of view   state = {     on: false   };    toggle = () => {     // Create a new "on" state to mount the Portal component via the button     this.setState({       on: !this.state.on     });   };   // Now, let's render the components   render() {     const { on } = this.state;     return (       // The div where that uses the Portal component child       <div>         <header>           <h1>Welcome to React</h1>         </header>         <React.Fragment>           // The button that toggles the Portal component state           // The Portal parent is listening for the event           <button onClick={this.toggle}>Toggle Portal</button>           // Mount or unmount the Portal on button click           <Portal>             {               on ?                 <h1>This is a portal!</h1>               : null             }           </Portal>         </React.Fragment>       </div>     );   } }

Since we want to toggle the Portal on and off, we need to make use of component state to manage the toggling. That’s basically a method to set a state of on to either true or false on the click event. The portal gets rendered when on is true; else we render nothing.

This is how the DOM looks like when the on state is set to true.

When on is false, the Portal component is not being rendered in the root, so the DOM looks like this.

More use cases

Modals are a perfect candidate for Portals. In fact, the React docs use it as the primary example for how Portals work:

See the Pen Example: Portals by Dan Abramov (@gaearon) on CodePen.

It’s the same concept, where a Portal component is created and a state is used to append the its child elements to the Modal component.

We can even insert data from an outside source into a modal. In this example, the App component lists users fetched from an API using axios.

See the Pen React Portal 3 by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

How about tooltips? David Gilberston has a nice demo:

See the Pen React Portal Tooptip by David Gilbertson (@davidgilbertson) on CodePen.

J Scott Smith shows how Portals can be used to escape positioning:

He has another slick example that demonstrates inserting elements and managing state:

Summary

That’s a wrap! Hopefully this gives you a solid base understanding of Portals as far as what they are, what they do, and how to use them in a React application. The concept may seem trivial, but having the ability to move elements outside of the DOM hierarchy is a handy way to make components a little more extensible and re-usable… all of which points to the core benefits of using React in the first place.

More information

The post Using React Portals to Render Children Outside the DOM Hierarchy appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,
[Top]

An Overview of Render Props in React

An Overview of Render Props in React

Using render props in React is a technique for efficiently re-using code. According to the React documentation, “a component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic.” To understand what that means, let’s take a look at the render props pattern and then apply it to a couple of light examples.

The render props pattern

In working with render props, you pass a render function to a component that, in turn, returns a React element. This render function is defined by another component, and the receiving component shares what is passed through the render function.

This is what this looks like:

class BaseComponent extends Component {   render() {     return <Fragment>{this.props.render()}</Fragment>;   } }

Imagine, if you will, that our App is a gift box where App itself is the bow on top. If the box is the component we are creating and we open it, we’ll expose the props, states, functions and methods needed to make the component work once it’s called by render().

The render function of a component normally has all the JSX and such that form the DOM for that component. Instead, this component has a render function, this.props.render(), that will display a component that gets passed in via props.

Example: Creating a counter

See the Pen React Render Props by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

Let’s make a simple counter example that increases and decreases a value depending on the button that is clicked.

First, we start by creating a component that will be used to wrap the initial state, methods and rendering. Creatively, we’ll call this Wrapper:

class Wrapper extends Component {   state = {     count: 0   };    // Increase count   increment = () => {     const { count } = this.state;     return this.setState({ count: count + 1 });   };    // Decrease count   decrement = () => {     const { count } = this.state;     return this.setState({ count: count - 1 });   };    render() {     const { count } = this.state;      return (       <div>         {this.props.render({           increment: this.increment,           decrement: this.decrement,           count: count         })}       </div>     );   } }

In the Wrapper component, we specify the methods and state what gets exposed to the wrapped component. For this example, we need the increment and decrement methods. We have our default count set as 0. The logic is to either increment or decrement count depending on the method that is triggered, starting with a zero value.

If you take a look at the return() method, you’ll see that we are making use of this.props.render(). It is through this function that we pass methods and state from the Wrapper component so that the component that is being wrapped by it will make use of it.

To use it for our App component, the component will look like this:

class App extends React.Component {   render() {     return (       <Wrapper         render={({ increment, decrement, count }) => (           <div>             <div>               <h3>Render Props Counter</h3>             </div>             <div>               <p>{count}</p>               <button onClick={() => increment()}>Increment</button>               <button onClick={() => decrement()}>Decrement</button>             </div>           </div>         )}       />     );   } }

Example: Creating a data list

The gain lies in the reusable power of render props, let’s create a component that can be used to handle a list of data which is obtainable from an API.

See the Pen React Render Props 2 by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.

What do we want from the wrapper component this time? We want to pass the source link for the data we want to render to it, then make a GET request to obtain the data. When the data is obtained we then set it as the new state of the component and render it for display.

class Wrapper extends React.Component {   state = {     isLoading: true,     error: null,     list: []   };    fetchData() {     axios.get(this.props.link)       .then((response) => {         this.setState({           list: response.data,           isLoading: false         });     })     .catch(error => this.setState({ error, isLoading: false }));   }    componentDidMount() {     this.setState({ isLoading: true }, this.fetchData);   }    render() {     return this.props.render(this.state);   } }

The data link will be passed as props to the Wrapper component. When we get the response from the server, we update list using what is returned from the server. The request is made to the server after the component mounts.

Here is how the Wrapper gets used:

class App extends React.Component {   render() {     return (       <Wrapper         link="https://jsonplaceholder.typicode.com/users"         render={({ list, isLoading, error }) => (           <div>             <h2>Random Users</h2>             {error ? <p>{error.message}</p> : null}             {isLoading ? (               <h2>Loading...</h2>             ) : (               <ul>{list.map(user => <li key={user.id}>{user.name}</li>)}</ul>             )}           </div>         )}       />     );   } }

You can see that we pass the link as a prop, then we use ES6 de-structuring to get the state of the Wrapper component which is then rendered. The first time the component loads, we display loading text, which is replaced by the list of items once we get a response and data from the server.

The App component here is a class component since it does not manage state. We can transform it into a functional stateless component.

const App = () => {   return (     <Wrapper       link="https://jsonplaceholder.typicode.com/users"       render={({ list, isLoading, error }) => (         <div>           <h2>Random Users</h2>           {error ? <p>{error.message}</p> : null}           {isLoading ? (             <h2>Loading...</h2>           ) : (             <ul>{list.map(user => <li key={user.id}>{user.name}</li>)}</ul>           )}         </div>       )}     />   ); }

That’s a wrap!

People often compare render props with higher-order components. If you want to go down that path, I suggest you check out this post as well as this insightful talk on the topic by Michael Jackson.

The post An Overview of Render Props in React appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]