I'd like some advices on best practises on using react - reactjs

As a way to learn new frameworks, I usually rewrite a small web app I made to handle a list of mp3s I use in a web radio. So, now, it's react's turn !
So basically, I render a table that has fields, and one of those td has a list of tags, as I use bootstrap for that purpose, I have bootstrap labels (that are called badges on bootstrap 4), so here's the minimum example of code I could have :
<td>
<span class="badge badge-success">tag1</span>
<span class="badge badge-success">tag2</span>
<span class="badge badge-success">tag3</span>
</td>
So, basically, to teach myself how to use a component inside a component by the example, I made a component to handle the tags (showing them, and later, have a popup to select them) but the way I have to render the data is so inelegant, I suppose there's some way to make it better.
here is the render() of the component :
render() {
return (
<span>
{ this.props.tags.map((tag) => {
return (
<span key={tag._id}>
<span className="badge badge-success">{tag.intitule}</span>
{' '}
</span>
);
})}
</span>
);
}
I suppose I could render the whole td to avoid a span, but then it would not be as reusable as withouth it. So, how can I make this prettier to read, and with a more elegant rendered code.

Rendering children components without a parent tag is now impossible, but there is an open GitHub issue that hopefully will let us avoid this limitation.
However, your code can be more elegant if you will convert it to:
render(){
const { tags } = this.props;
return (
<div>
{ tags.map(tag => <span key={tag._id} className="badge badge-success">{tag.intitule}</span>) }
</div>
)
}
Update #1
You can also separate "tag" to a stateless functional component, like so:
const Tag = ({tag}) => (
<span className="badge badge-success">{tag.intitule}</span>
);
then, in other, more complex component, you can do
class ComplexComponent extends React.Component{
render(){
const { tags } = this.props;
return (
<div>
{ tags.map(tag => <Tag key={tag._id} tag={tag} />) }
</div>
)
}
}
Check this fiddle.

I think I would do the mapping out of the return. Like this :
render() {
let labels = this.props.tags.map((tag) => {
return (
<span key={tag._id}>
<span className="badge badge-success">{tag.intitule}</span>
{' '}
</span>
);
})
return (
<span>
{ labels }
</span>
);
}
You could also create a small component for your labels (a little bit overkill), like this:
export class MyLabel extends React.Component {
render() {
const {tag} = this.props;
return <span key={tag._id}>
<span className="badge badge-success">{tag.intitule}</span>
{' '}
</span>
}
}
... // In your main Component
render() {
let labels = this.props.tags.map((tag) => {
return <MyLabel tag={tag}/>;
})
return (
<span>
{ labels }
</span>
);
}

Related

How would i strikethorugh list items which are completed in Todolist in React?

Here is my code in which i wanted to strike through the completed list item.
export default class Todo extends Component{
constructor(){
super();
this.state={
text:"",
todoList:[],
strikeThrough:[]
}
}
I am just getting confuse how to use the css property of line through in react components and how would it take that particular index to be updated.
doneTodo=index => ()=>{
const list3=[...this.state.todoList];
this.setState({todoList:list3})
};
render() {
let {text,todoList}=this.state;
return(
<div className="App-header">
<div className="childApp">
<h1 style={{textAlign: "center"}}>To-Do APP!</h1>
<h3>You have {todoList.length} Todos</h3>
<br/>
<ul className="list-group">
{todoList.map((val,index) => {
return (
<div className="mytodo">
<li className="list-group-item">
<span classname="spname"> {val} </span>
<button type="button" onClick={this.doneTodo} className="btn btn-default btn-s pull-right remove-item">Done</button>
<button key={index} id={index} class="btn btn-default btn-s pull-right remove-item" onClick={this.delTodo(index)}>
<span className="glyphicon glyphicon-remove"></span>
</button>
</li>
The easiest way to do this (not necessary the only way), and the way I'd recommend you since you seem like a react beginner is like this:
since you already seem to have a class mytodo for all your todos, add the following css selector to your stylesheet:
.mytodo .done {
text-decoration: line-through;
}
now what you will need to do, is add the class done to all your todos that are done.
To do that, I would suggest you change your code structure a bit, mainly by adding a isDone property to your todo object instead of using two arrays.
Now you would need to conditionally render the css class like this:
<div className=`myTodo ${todo.isDone ? 'done' : ''}`>
...
</div>
The only thing left do do now is to toggle the isDone property of each todo object. For that you could write a function like this:
setDone = todoId => {
this.setState(prevState => ({
todoList: prevState.todoList.map(el => (el.id === todoId ? {...el, isDone: true} : el))
}));
}
Lastly call the function on click and pass the id. Maybe with something like this:
<button onClick={() => setDone(todo.id)}>set done</button>

Switch to different component after button click with React

I am absolutely new to React and need to create a button to take the user from a current component (MovieView) to the main one (MainView). I managed to create it properly and I used onClick to trigger the display of MainView. I know I cannot call a class from a function (so as console says), so I created a second function to be called and trigger MainView. But as you may wonder, it does not work. This is the code I am using:
import React from 'react';
import { MainView } from '../main-view/main-view';
function NewButton() {
return <MainView />;
}
export class MovieView extends React.Component {
constructor() {
super();
this.state = {};
}
render() {
const { movie } = this.props;
if (!movie) return null;
return (
<div className="movie-view">
<img className="movie-poster" src={movie.imagePath} />
<div className="movie-title">
<span className="label">Title: </span>
<span className="value">{movie.title}</span>
</div>
<div className="movie-description">
<span className="label">Description: </span>
<span className="value">{movie.plot}</span>
</div>
<div className="movie-genre">
<span className="label">Genre: </span>
<span className="value">{movie.genre.name}</span>
</div>
<div className="movie-director">
<span className="label">Director: </span>
<span className="value">{movie.director.name}</span>
</div>
<div className="back-movies">
<button onClick={NewButton}>Back</button>
</div>
</div>
);
}
}
Could you any seeing this to point the path to take or how I can call this MainView class properly. I understand it's a simple task, but I am afraid I still haven't absorbed React principles yet. Thanks in advance to you all.
Without introducing additional dependencies, probably the easiest way for this example is to use state to track if the button has been clicked. If it has been clicked, render MovieView, if not render MainView.
For this you need to following:
Set state variable that tracks that MainView should be rendered in onClick event. (boolean will probably suffice)
in render():
if the state var is false, render the content of MovieView.
if the state var is true, render the MainView component.
Implementation details are left as an exercise :)
If you're using a router already (like react-router) you could probably just update the url to an url that is linked to the MainView component..
I make the Class into a Function Component instead, and implement it like this :-)
import React from "react";
import { MainView } from '../main-view/main-view';
export const MovieView = ({ movie }) => {
const { showMainView, setShowMainView } = React.useState(false);
if (!!movie) {
return null;
}
function toggleMainView() {
setShowMainView(true);
}
return (
<React.Fragment>
{ showMainView ? (
<MainView />
) : (
<div className="movie-view">
<img className="movie-poster" src={movie.imagePath} />
<div className="movie-title">
<span className="label">Title: </span>
<span className="value">{movie.title}</span>
</div>
<div className="movie-description">
<span className="label">Description: </span>
<span className="value">{movie.plot}</span>
</div>
<div className="movie-genre">
<span className="label">Genre: </span>
<span className="value">{movie.genre.name}</span>
</div>
<div className="movie-director">
<span className="label">Director: </span>
<span className="value">{movie.director.name}</span>
</div>
<div className="back-movies">
<button onClick={() => toggleMainView()}>Back</button>
</div>
</div>
)}
</React.Fragment>
);
};
export default MovieView;
If you need to add or remove components or show a new view based on an user action in react we need to do this via state .
import React from 'react';
import { MainView } from '../main-view/main-view';
export class MovieView extends React.Component {
constructor() {
super();
this.state = {
showMainView: false;
};
}
toggleMainView = () => this.setState(prevState => ({ showMainView:
!prevState.showMainView}))
render() {
const { movie } = this.props;
const { showMainView } = this.state;
if (!movie) return null;
return (
<div className="movie-view">
<img className="movie-poster" src={movie.imagePath} />
<div className="movie-title">
<span className="label">Title: </span>
<span className="value">{movie.title}</span>
</div>
<div className="movie-description">
<span className="label">Description: </span>
<span className="value">{movie.plot}</span>
</div>
<div className="movie-genre">
<span className="label">Genre: </span>
<span className="value">{movie.genre.name}</span>
</div>
<div className="movie-director">
<span className="label">Director: </span>
<span className="value">{movie.director.name}</span>
</div>
<div className="back-movies">
<button onClick={this.toggleMainView}>Back</button>
</div>
// render the mainView based on the state
{ showMainView && <MainView />}
</div>
);
}
}
I reached a solution that I'd not do with the help I had here. I have first pasted here some pieces of code but, I believe it can be helpful to give the whole answer.
So, the question I placed here was regarding a component called <MovieView, that was a part of an app that interacted with another component (in another file) called MainView.
The solution was the insertion of the method onClick on the live 14 of <MovieView> and the button on line 39 that you can see at this file on Github. If the file was updated, check the version for November 22nd.
The solution had, however, been found in the main component, <MainView>, adding code on lines 48 and 49. Here is the link to the file: https://github.com/cgobbet/react-client/blob/master/src/components/main-view/main-view.jsx
The repository with the whole app is on this page: https://github.com/cgobbet/react-client.
And sorry for the reviewers (I had accidentally deleted part of the fantastic answer submitted by #nick-miller.

How to add color to a specific part of a string in React using replace?

I'd like to make all the numbers in a string red and then render it with React.
Here's what I'm trying to do (I've made an app using create-react-app and replaced the contents of App.js with my own):
import React, { Component } from 'react';
import './App.css';
class App extends Component {
render() {
const str = 'foo123bar';
const strColor =
str.replace(/\d+/, match => <span style={{color: 'red'}}> {match} </span> );
return (
<div className="App">
{strColor}
</div>
);
}
}
export default App;
As a result only the line foo[object Object]bar was rendered in the viewport.
So how should inline styling be added to JSX?
I was able to solve this by using 'dangerouslySetInnerHTML'.
class App extends React.Component {
render() {
let str = 'foo123bar';
const strColor = str.replace(/\d+/, match => `<span style="color: red">${match} </span>` );
return (
<div className="App"
dangerouslySetInnerHTML={{__html:
strColor}}>
</div>
);
}
}
"So how should inline styling be added to JSX?"
To answer to your stated question, your line:
const strColor = str.replace(/\d+/, match => <span style={{color: 'red'}}> {match} </span> );
is returning a string - so the object statement style={{color: 'red'}}>
will not be escaped.
Add the inline styling with a string definition instead, reference the double quotes and removed curly braces:
<span style="color: red"> {match} </span>
You can add more styles by separating the key: value pairs with commas:
<span style="color: red, text-decoration: underline"> {match} </span>
Note that this will not work
The question that you're really trying to answer is how to replace parts of a string, with a component (in this case a <span> tag with styling. This issue is documented in the react github issues page, note that there are many options without requiring you to dangerously set your inner HTML as noted in several previous answers: https://github.com/facebook/react/issues/3386
You can't insert HTML into a string for it to be rendered in React, this exists as a protection from XSS.
What you can do in a case like this is something like:
const str = 'foo123bar';
const coloredStr = str.match(/\d+/);
const [before, after] = str.split(/\d+/)
return (
<div className="App">
{before}
{coloredStr && coloredStr[0] &&
<span style="color: red">{coloredStr[0]}</span>
}
{after}
</div>
);
For a more complex example you will need more complex logic. E.g. multiple parts can be styled - you can find all the matches and the non matching parts and put them in a list in the right order with an indicator should you use the span or not. Something like:
list.map((elem) => elem.isColored ? <span style="color: red">{elem.value}</span> : elem.value)
EDIT
As mentioned in the comments, here is an implementation for multiple elements:
const str = 'foo123bar456baz897ban';
let strCopy = str;
const list = [];
while(strCopy) {
const text = strCopy.split(/\d+/, 1)[0];
list.push(text);
const match = strCopy.match(/\d+/);
if (!match) {
break;
}
list.push(match[0]);
strCopy = strCopy.substring(match.index + match[0].length);
}
return (
<div className="App">
{list.map((elem, index) => index % 2 === 0
? elem
: <span style="color: red">{elem}</span>
)}
</div>
);

Disable all buttons on click in react

So I have a component, called AddButton
export default class AddButton extends React.Component {
constructor(props) {
super(props);
}
addItem(e) {
this.btn.setAttribute('disabled', 'disabled');
this.props.addItem(e.target.getAttribute('data-row-index'))
}
render() {
return (
<div className="row">
<div className="col-md-12 text-center">
<button ref={btn => {this.btn = btn }} className="btn btn-success" onClick={this.addItem.bind(this)} data-row-index={this.props.rowIndex}>Add</button>
</div>
</div>
)
}
}
Some where else in the code I do:
if (this.props.addButton) {
rows.push(
<td key="add">
<AddButton
addItem={this.props.addItem}
rowIndex={this.props.rowIndex}
/>
</td>
)
}
So I have at one time 50 of these in a table at the end of the row. When one is clicked I wanted to disable all the buttons.
So as you can see I have done, in addItem(e):
addItem(e) {
this.btn.setAttribute('disabled', 'disabled');
this.props.addItem(e.target.getAttribute('data-row-index'))
}
But when I test this, only the button after the one is clicked is disabled. I want them all to be disabled
Any way I could modify this to achieve that?
React components have syntax like HTML DOM but they are not, they are modules and every time you use that module for example in your table it would be a new instance of that module. So if you want to share a state between them you have to pass it as props to them and you shouldn't treat them as HTML nodes.

Multiple React rc-sliders ... no idea how to find out which one is actually changing

I have a variable list of included services for my product and I have a rc-slider component for each of these services to de/increase the amount a users wants of each of the services (like training days, user licenses, ...)
I've got to the code below which gives me a slider for each of my services BUT ... each of the rc-sliders only return the numeric value of the slider but nothing for me to identify which slider is actually being changed.
<ul className="included-services-amounts">
{productIncludedServices.map( function(productIncludedService, i) {
return (
<li key={i}>
<div>{productIncludedService.name}</div>
<Slider dots min={productIncludedService.range[0]} max={productIncludedService.range[1]} step={1} defaultValue={10} onChange={updateIncludedServicesFunc.bind(this)} />
</li>
)
})}
</ul>
Sooo, is there any way to pass something more aside from "this" in
{updateIncludedServicesFunc.bind(this)}
or some alternative way to find out which slider component is actually getting changed?
yup, there are several ways to do this.
class App extends React.Component {
method1(e) {
console.log(e.target.getAttribute("data-params"));
}
method2 = params => {
console.log(params);
};
method3(params) {
console.log(params);
}
render() {
return (
<div className="App">
<button
data-params="params from method 1"
onClick={this.method1.bind(this)}
>
method 1
</button>
<button
onClick={() => {
this.method2("params from method 2");
}}
>
method 2
</button>
<button onClick={this.method3.bind(this, "params from method 3")}>
method 3
</button>
</div>
);
}
}
live demo on sandbox

Resources