I'm new to React and looking for some best practices. I'm making a Chrome extension that's a small widget. The widget has 3 sections. Call Section A, Section B, and Section C. At the top of the widget I want to have buttons like so:
[logo] | Section A | Section B | Section C |
[
Panel Content (Default Section A)
]
And when one of those section links are clicked, the content in the panel below updates with the relevant content for that section.
My first thought was to render all of them, and then just hide jQuery show/hide() on the panels. It works, but I'd rather not do that because each panel loads some data asynchronously and I'd rather not pay that price up front if the user never clicks on the latter 2 links.
I've created React components for each section so their easy to swap out.
I then tried this:
showSectionB: function(){
React.renderComponent(
<SectionBList person={this.props.person} />,
document.querySelector('.main .panel')
);
},
render: function() {
return (
<div className="main">
<div className="actions">
<button className="T-I-ax7" onClick={this.showSectionB}>Section B</button>
</div>
<div className="panel">
<SectionAList person={this.props.person} />
</div>
</div>
);
}
It felt more logical, but feels weird I'm reaching inside a component for the container to place the component. On top of that, the whole browser locked up and gave me this message after the panel switched:
React attempted to use reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server.
Is there a better way to hand this?
The key to React is to always go through render() to render your application. User interaction should only fire events that trigger a re-render(). In your example onClick should call setState() to do that.
getInitialState: function () {
return {
section: "a"
};
},
showSectionB: function(){
// Update the component's state for a re-render().
this.setState({
section: "b"
});
},
render: function() {
if (this.state.section == "a") {
var section = <SectionAList person={ this.props.person } />;
}
else if (this.state.section == "b") {
var section = <SectionBList person={ this.props.person } />;
}
return (
<div className="main">
<div className="actions">
<button className="T-I-ax7" onClick={ this.showSectionB }>Section B</button>
</div>
<div className="panel">
{ section }
</div>
</div>
);
}
Updated thanks for the comment!
Related
I'm learning how to use vue.js
I've a shared hosting plan where I can only use html. I'm fetching the data I need using axios and a remote wordpress installation that will act as a backend only. What I need to know, is how I can change the DOM content of the index.html using vue if the user click on a link and I need to change the layout of the page because a different presentation for the contents is needed?
See the example:
<div id="vue-app">
link to layout 2
<div class="col-12">starting layout </div>
</div>
// after the user click the link (v-on:click) the layout change
<div id="vue-app">
link to layout 1
// layout change
<div class="col-6">new layout </div>
<div class="col-6">new layout </div>
</div>
Please read up on Conditional Rendering in Vue.js.
You can have a boolean variable in the data compartment of your script tag and change it on click.
And in the tags put v-if="your_bool_variable".
<div id="vue-app" v-if="layout_switch">
link to layout 2
<div class="col-12">starting layout </div>
</div>
// after the user click the link (v-on:click) the layout change
<div id="vue-app" v-else>
link to layout 1
// layout change
<div class="col-6">new layout </div>
<div class="col-6">new layout </div>
</div>
Negate the boolean variable at the #click event.
Data could look like the following:
<script>
export default {
name: "YourComponent",
data: () => {
return {
layout_switch: true
}
},
methods: {
changeLayout() {
this.layout_switch = !this.layout_switch;
}
}
}
</script>
I've been trying to get the Facebook event plugin to work in my create-react-app and can't figure it out.
In my Events.js file I added this:
<div id="fb-root"></div>
<div className="fb-page" data-href="https://www.facebook.com/[artistname]" data-tabs="events"
data-width="" data-height="" data-small-header="true" data-adapt-container-width="true"
data-hide-cover="false" data-show-facepile="true">
<div className="fb-xfbml-parse-ignore">
<blockquote cite="https://www.facebook.com/[artistname]/events">
[artistname] - coming events
</blockquote>
</div>
</div>
The SDK is imported in index.html
Everything works fine when I load the actual events page, but after clicking on "Start" and going back to "Events" the plugin disappears.
Some say to put
componentDidMount(){
window.FB.XFBML.parse()};
}
in the component, but my Event.js is a function.
I'm running out of google links now, any help is appreciated.
Easiest solution is to use the iframe option, but that doesn't directly answer your question.
Answer 1: use the useEffect hook
Example CodeSandbox here
Instead of using the componentDidMount lifecycle method from class components, you can use the useEffect hook:
useEffect(() => {
window.FB.XFBML.parse();
}, []);
This will call window.FB.XFBML.parse once per render.
Note: You may want to wrap that with an if (window.FB) to catch instances where window.FB didn't load
Answer 2: Convert to class component
You can of course convert your component to the old class based style:
export default class Events {
componentDidMount() {
window.FB.XFBML.parse();
}
render() {
return (
<>
{/* ... */}
<div
className="fb-page"
data-href={`https://www.facebook.com/${artistName}`}
data-tabs="events"
data-width=""
data-height=""
data-small-header="true"
data-adapt-container-width="true"
data-hide-cover="false"
data-show-facepile="true"
>
<div className="fb-xfbml-parse-ignore">
<blockquote cite={`https://www.facebook.com/${artistName}/events`}>
<a
href={`https://www.facebook.com/${artistName}/events`}
target="_blank"
>
{artistName} - coming events
</a>
</blockquote>
</div>
</div>
</>
);
}
}
So I have a DIV element that, when pressed, I want to open a new tab in a different window. The only problem is, whenever the page is refreshed or any other div element is pressed, th function is initiated as well and opens a page in another window. I'll include my code below, but I'm not sure why this is happening seeing as how I'm using onClick={window.open("https://www.thechinesewriter.com") Like I said, I only want a new tab to open when the div is pressed, not when any other items on the page are clicked or even when the page itself is refreshed.
import React from "react";
import "./Column1.css";
class Column1 extends React.Component {
render() {
return(
<React.Fragment>
<div className="rectImage">
<img className="imagePost" src={this.props.image} />
</div>
<div onClick={window.open("https://www.thechinesewriter.com")} className="downloadBut1">
<h2>
Source
</h2>
</div>
<div className="downloadBut2">
<h2>
Repository
</h2>
</div>
</React.Fragment>
)
}
}
export default Column1;
You need to call the function as a callback like this.
<div onClick={() => window.open("https://www.thechinesewriter.com")} className="downloadBut1">
Otherwise this gets called every time the component gets rendered.
You use an expression, which will execute everytime the component is rendered.
<div onClick={window.open("https://www.thechinesewriter.com")} className="downloadBut1">
You probably mean to use a function
<div onClick={() => window.open("https://www.thechinesewriter.com")} className="downloadBut1">
I am trying to learn React. I already have a good grasp of javascript. I am trying to learn by creating a small app that is basically a task manager. In my case it's for grocery related items. I have a fiddle created here. Could you please take a look at how I composed the react code and let me know if this is the best approach to building with components/classes? You can see that I have nested components. I am not sure if there is a better way of doing this.
Finally, I wan't a new "add-item-row" created every time a user clicks on the big blue Add button. Right now one is showing be default but I don't want any showing by default. I want one created (add-item-row, div) only when a user clicks on the Add button.
Here is the fiddle.
https://jsfiddle.net/j0mpsbh9/4/
<div id="app" class="container">
<script type="text/babel">
var AddItemWrapper = React.createClass({
render: function() {
return (
<div>
<div className="row">
<AppTitle />
<AddItemForm />
</div>
<AddItemRow />
</div>
);
}
});
var AppTitle = React.createClass({
render: function() {
return (
<div>
<h1>Grocery List</h1>
</div>
);
}
});
var AddItemForm =React.createClass({
render: function() {
return (
<div>
<div className="col-sm-6 col-lg-6">
<div className="form-group">
<label htmlFor="enter-grocery-item" className="sr-only">Enter Grocery Item</label>
<input type="text" className="form-control" id="enter-grocery-item" placeholder="Enter Grocery Item" />
</div>
</div>
<div className="col-sm-6 col-lg-6">
<button type="button" id="add" className="btn btn-block btn-info">Add <span className="glyphicons circle_plus"></span></button>
</div>
</div>
);
}
});
var AddItemRow =React.createClass({
render: function() {
return (
<div className="add-item-row">
<div className="row">
<div className="col-sm-12 grocery-items">
<div className="col-sm-6">
<div className="form-group">
<label htmlFor="grocery-item" className="sr-only">Grocery Item</label>
<input type="text" className="form-control" id="grocery-item" placeholder="" />
</div>
</div>
<div className="col-sm-6 center">
<button type="button" className="btn btn-blockx btn-warning"><span className="glyphicons pencil"></span></button>
<button type="button" className="btn btn-blockx btn-lgx btn-danger"><span className="glyphicons remove"></span></button>
<button type="button" className="btn btn-blockx btn-lgx btn-success"><span className="glyphicons thumbs_up"></span></button>
</div>
</div>
</div>
</div>
);
}
});
ReactDOM.render(
<AddItemWrapper />,
document.getElementById('app')
);
</script>
</div>
You have a good start here! Your component hierarchy is organized in a sensible way. However you are missing any kind of interactivity or internal state.
The main way you make React components interactive is by using state plus event callbacks which modify said state. "State" is pretty self explanatory - it describes values inherent to how the components looks and behaves, but which change over time. Every time a React component's state is altered (with this.setState()) that component will re-render (literally by re-running the render() function) to reflect the changes.
First let's edit your AddItemWrapper class to keep track of some internal state when it is first mounted. We know that you want to have multiple rows of data, so let's give it an empty array to store future information about rows:
getInitialState(){
return {rows: []};
},
Now instead of rendering a single AddItemRow directly, we'll render a dynamic set of rows that is based on the current component state. Array.map() is perfect for this and a common use case in React:
{this.state.rows.map(function(ea, i){
return <AddItemRow initialItemName={ea} key={ea + "-" + i} />
})}
Basically what that does is take every entry in the array AddItemWrapper.state.rows array and renders it as an AddItemRow component. We give it two properties, initialItemName and key. "initialItemName" will just tell the child component what its name was when it was first added, and "key" is a unique string that allows React to differentiate components from their siblings.
Now we've set up AddItemWrapper to properly render rows based on its internal state. Next we have to modify AddItemForm so that it will react to user input and trigger new rows being added.
In AddItemForm, first we need to add a "ref" to the input text box. This is so that React can identify and read data from this HTML element after it is rendered:
<input ref={function(el){this.inputElement = el;}.bind(this)} ... />
Then give the button element a callback that will trigger when it's clicked:
<button onClick={this.handleClick} ... />
Finally write the callback handler itself:
handleClick(){
this.props.onAdd(this.inputElement.value);
this.inputElement.value = "";
}
Notice how this callback is calling this.props.onAdd()? That means we need to pass in a callback function from the parent (AddItemWrapper) to this component to use. This is how we communicate between parents and children in React: pass a function from a parent to a child which will be triggered from within the child, but will effect the parent.
In AddItemWrapper we make sure AddItemForm has access to the callback function:
AddItemForm onAdd={this.onAdd} />
And then we write the callback function itself:
onAdd(newItem){
var newRows = this.state.rows.slice();
newRows.push(newItem);
this.setState({rows: newRows});
}
Notice how we're copying the old array held in state (using Array.slice()), push a new item into the new array, and then update state with the new array? Never mutate state directly; ALWAYS copy it, modify the copy, and then update state with the new copy.
Almost done. We've created a way for AddItemWrapper to render its rows, and a way for AddItemForm to create new rows. Now we edit AddItemRow to render in a way that maintains its own internal state too.
First make sure it initializes its own state when it's mounted. We'll have it keep track of a string value, which initially is the same as what the user entered into the text box when they pressed "Add", but because it's kept in AddItemRow.state it can be modified later by the user:
getInitialState() {
return {itemName: this.props.initialItemName}
}
Now that the row name is kept in the component state, we can render it in the HTML like this:
<input value={this.state.itemName} ... />
Here's what it looks like when you put it all together!
There are obviously more features that you would want to add, such as letting the user edit, move, or delete a row entry. I'll leave those exercises up to you. I highly recommend you read through all of the official documentation as well as do a few tutorials to get your head in the game. It's obvious that you have a bit of experience under your belt given what you had so far, but getting the hang of how state, render(), and callbacks work takes some practice. Good luck!
I've been learning ractive.js for few weeks and I am trying to build a feature within a larger webpage that lists out three items. Each list has it's own title and description. Each list is a component (the list-component) with its own styles and javascript (each component is a separate html). I also have a component (description-component) that is responsible for writing out the title and description and it is in a separate html file. The problem I am having is 'importing' or bringing in that description-component into the list-component so the output in the main index html file is this:
<description title="Name">
<list-items/>
</description>
So far I looked into the yield directive and tried some examples but that was a simple example using two components on the same document. I am not sure if that is the correct way. This is what is in the description-component html file:
<p>{{title}}</p>
I tried using ractive-load.js and load up the description-component html for every list-component file like in this example on their github:
Ractive.load({
Foo: 'my-components/foo.html',
Bar: 'my-components/bar.html'
}).then( function ( components ) {
var foo = new components.Foo({
el: 'body',
data: { ... }
});
var bar = new components.Bar({
el: 'body',
data: { ... }
});
}).catch( handleError );
and that seemed like an overkill and thought there must be a better way. How would I go about approaching this?
The loading of Ractive components is not directly related to handling nested components and passing yielded content to components.
Ractive.load works by fetching one or more html files that it then resolves to Components. From your code samples, it seems you've got that part working. For larger apps, I usually use a build tool plugin to pre-bundle all my Ractive components so they're deliverable in one file or even rolled into my main bundle js.
However the components are made available, they need to be registered on either the consuming component (or a view parent) or globally:
Ractive.load({
Foo: 'my-components/foo.html',
Bar: 'my-components/bar.html'
}).then( function ( components ) {
Ractive.components.foo = components.Foo;
Ractive.components.bar = components.Bar;
// now create your actual top-level view instance(s)
}).catch( handleError );
In component architectures, you create trees or bushes of components. I usually only have one top level app component, but it certainly is feasible to create multiple trees that start at different places in the DOM.
For simplicity, continuing on the above example, let's create a generic ractive instance that uses the two components Foo and Bar we registered (notice we use the property name we assigned to Ractive.components):
const ractive = new Ractive({
el: document.body,
sayHello() {
alert('hello from main Ractive instance');
},
template: `
<h1>my kewl app</h1>
<foo>
<h3 on-click="sayHello()">hello world</h3>
<bar bizz="{{buzz}}"></bar>
</foo>
`
});
In this case we're passing some content (html and our bar component) to the foo component by including it as the <foo> element content.
How this content is used depends on the foo template. There are two choices:
<div>
<h2>foo component template</h2>
{{>content}}
<p>some more stuff</p>
</div>
In this example, we're using the built-in partial "content" to tell the template to put provided content in the {{>content}} slot. In this case the provided content is passed like a partial, and any directives will be applied against the foo component. So in this example, clicking on the h3 header will try and run foo.sayHello(). And when passing the bizz data to the bar component, Ractive will start looking in the foo component for buzz.
Often, this isn't what you want. What you would rather have happen is for the parent to own the directives. So instead the foo template would look like:
<div>
<h2>foo component template</h2>
{{yield}}
<p>some more stuff</p>
</div>
And now when h3 is clicked, it calls the main ractive.sayHello() method as the content was passed to be rendered in the DOM by the the foo component, but it was still owned by the passing instance. Likewise Ractive will start look for buzz in the main instance, not foo.
With yield you can also name multiple partials to be passed:
<!-- "foo" template: -->
<div>
<header>{{yield header}}</header>
<section>
<div>something here</div>
<div>{{yield message}}</div>
</section>
</div>
<!-- using "foo": -->
<div>
<foo>
{{#partial header}}
<h2>This is the header to use</h2>
{{/partial}}
{{#partial message}}
<p>This is the message to use, with a bar component to boot</p>
<bar></bar>
{{/partial}}
</foo>
</div>