How to Divide React Components into Presentational and Container Components - javascript

Still new to react and redux and have been working on a MERN user registration application which I got working now.
In the redux documentation I found the creators recommend splitting their code up into two types of components when integrating redux with react: Presentational (concerns with how things look) and Container (concerns with how things work). See https://redux.js.org/basics/usagewithreact.
I think this would allow for better management and scalability of the application.
For people unfamiliar, here is a good explanation of the advantages: https://www.youtube.com/watch?v=NazjKgJp7sQ
I only struggle in grasping the concept and rewriting the code in such a way.
Here is an example of a post component I written using to display user created comments. It is receiving the data from the post in a higher-level component passed down as props. In the return I have all my markup with bootstrap styling applied. I am subscribing to redux actions I imported and using by creating event handlers.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
import { deletePost, addLike, removeLike } from '../../actions/postActions';
class PostItem extends Component {
onDeleteClick(id) {
this.props.deletePost(id);
}
onLikeClick(id) {
this.props.addLike(id);
}
onUnlikeClick(id) {
this.props.removeLike(id);
}
findUserLike(likes) {
const { auth } = this.props;
if (likes.filter(like => like.user === auth.user.id).length > 0) {
return true;
} else {
return false;
}
}
render() {
const { post, auth, showActions } = this.props;
return (
<div className="card card-body mb-3">
<div className="row">
<div className="col-md-2">
<a href="profile.html">
<img
className="rounded-circle d-none d-md-block"
src={post.avatar}
alt=""
/>
</a>
<br />
<p className="text-center">{post.name}</p>
</div>
<div className="col-md-10">
<p className="lead">{post.text}</p>
{showActions ? (
<span>
<button
onClick={this.onLikeClick.bind(this, post._id)}
type="button"
className="btn btn-light mr-1"
>
<i
className={classnames('fas fa-thumbs-up', {
'text-info': this.findUserLike(post.likes)
})}
/>
<span className="badge badge-light">{post.likes.length}</span>
</button>
<button
onClick={this.onUnlikeClick.bind(this, post._id)}
type="button"
className="btn btn-light mr-1"
>
<i className="text-secondary fas fa-thumbs-down" />
</button>
<Link to={`/post/${post._id}`} className="btn btn-info mr-1">
Comments
</Link>
{post.user === auth.user.id ? (
<button
onClick={this.onDeleteClick.bind(this, post._id)}
type="button"
className="btn btn-danger mr-1"
>
<i className="fas fa-times" />
</button>
) : null}
</span>
) : null}
</div>
</div>
</div>
);
}
}
PostItem.defaultProps = {
showActions: true,
};
PostItem.propTypes = {
deletePost: PropTypes.func.isRequired,
addLike: PropTypes.func.isRequired,
removeLike: PropTypes.func.isRequired,
post: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
};
const mapStateToProps = state => ({
auth: state.auth,
});
export default connect(mapStateToProps, { deletePost, addLike, removeLike })(PostItem);
As you can see is the code not as neat and compact as I would like. My goal is to make the presentational component unaware of redux, and do all styling and bootstrap stuff here, while the container component have the redux and connect functionalities. Does anyone know how I should approach this?
I saw people using connect to link these types components together:
const PostItemContainer = connect(
mapStateToProps,
{ deletePost, addLike, removeLike }
)(PostItem);
export default PostItemContainer;
But I have no idea how to achieve this in practice.
If you could help me explain and provide some example code that would be amazing.
Thanks in advance!

I would always put my html like ( presentation ) code in another file, which in react they call stateless component,
The key component is PostItemComponent which does not know anything about redux.
see the code below :
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
import { deletePost, addLike, removeLike } from '../../actions/postActions';
const PostItemComponent = ({
post,
showActions,
auth,
onLikeClick,
findUserLike,
onUnlikeClick,
onDeleteClick
}) => {
return (
<div className="card card-body mb-3">
<div className="row">
<div className="col-md-2">
<a href="profile.html">
<img
className="rounded-circle d-none d-md-block"
src={post.avatar}
alt=""
/>
</a>
<br />
<p className="text-center">{post.name}</p>
</div>
<div className="col-md-10">
<p className="lead">{post.text}</p>
{showActions ? (
<span>
<button
onClick={(event) => onLikeClick(event, post._id)}
type="button"
className="btn btn-light mr-1">
<i
className={classnames('fas fa-thumbs-up', {
'text-info': findUserLike(post.likes)
})}
/>
<span className="badge badge-light">{post.likes.length}</span>
</button>
<button
onClick={(event) => onUnlikeClick(event, post._id)}
type="button"
className="btn btn-light mr-1"
>
<i className="text-secondary fas fa-thumbs-down" />
</button>
<Link to={`/post/${post._id}`} className="btn btn-info mr-1">
Comments
</Link>
{post.user === auth.user.id ? (
<button
onClick={(event) => onDeleteClick(event, post._id)}
type="button"
className="btn btn-danger mr-1"
>
<i className="fas fa-times" />
</button>
) : null}
</span>
) : null}
</div>
</div>
</div>
);
};
class PostItem extends Component {
constructor(props) {
super(props);
this.onDeleteClick = this.onDeleteClick.bind(this);
this.onLikeClick = this.onLikeClick.bind(this);
this.onUnlikeClick = this.onUnlikeClick.bind(this);
this.findUserLike = this.findUserLike.bind(this);
}
onDeleteClick(event, id) {
event.preventDefault();
this.props.deletePost(id);
}
onLikeClick(event, id) {
event.preventDefault();
this.props.addLike(id);
}
onUnlikeClick(event, id) {
event.preventDefault();
this.props.removeLike(id);
}
findUserLike(likes) {
const { auth } = this.props;
if (likes.filter(like => like.user === auth.user.id).length > 0) {
return true;
} else {
return false;
}
}
render() {
const { post, auth, showActions } = this.props;
return (
<PostItemComponent
post={post}
auth={auth}
showActions={showActions}
onDeleteClick={this.onDeleteClick}
onLikeClick={this.onLikeClick}
onUnlikeClick={this.onUnlikeClick}
/>
);
}
}
PostItem.defaultProps = {
showActions: true,
};
PostItem.propTypes = {
deletePost: PropTypes.func.isRequired,
addLike: PropTypes.func.isRequired,
removeLike: PropTypes.func.isRequired,
post: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
};
const mapStateToProps = state => ({
auth: state.auth,
});
export default connect(mapStateToProps, { deletePost, addLike, removeLike })(PostItem);

This is a very similar suggestion to #jsDevia's answer but I don't create a separate component here since you said your Post component is already connected to Redux. So, you can grab all action creators and state there and pass those to your PostItem component.
The second difference is I use a functional component instead of class component since you don't need any state or lifecycle method here.
The third difference is a small one. I removed all binding from your onClick handlers. For the this scope issue, I'm using arrow functions for the handlers. Again, we don't need any argument, like post._id to pass those function because we already have post as a prop here. This is the beauty of separating our components.
Using bind or an arrow function in callback handlers cause some performance issues for larger apps which have so many components like Post. Since those functions recreated every time this component renders. But, using the function reference prevents this.
const PostItem = ({
post,
deletePost,
addLike,
removeLike,
auth,
showActions,
}) => {
const onDeleteClick = () => deletePost(post._id);
const onLikeClick = () => addLike(post._id);
const onUnlikeClick = () => removeLike(post._id);
const findUserLike = likes => {
if (likes.filter(like => like.user === auth.user.id).length > 0) {
return true;
} else {
return false;
}
};
return (
<div className="card card-body mb-3">
<div className="row">
<div className="col-md-2">
<a href="profile.html">
<img
className="rounded-circle d-none d-md-block"
src={post.avatar}
alt=""
/>
</a>
<br />
<p className="text-center">{post.name}</p>
</div>
<div className="col-md-10">
<p className="lead">{post.text}</p>
{showActions ? (
<span>
<button
onClick={onLikeClick}
type="button"
className="btn btn-light mr-1"
>
<i
className={classnames("fas fa-thumbs-up", {
"text-info": findUserLike(post.likes),
})}
/>
<span className="badge badge-light">{post.likes.length}</span>
</button>
<button
onClick={onUnlikeClick}
type="button"
className="btn btn-light mr-1"
>
<i className="text-secondary fas fa-thumbs-down" />
</button>
<Link to={`/post/${post._id}`} className="btn btn-info mr-1">
Comments
</Link>
{post.user === auth.user.id ? (
<button
onClick={onDeleteClick}
type="button"
className="btn btn-danger mr-1"
>
<i className="fas fa-times" />
</button>
) : null}
</span>
) : null}
</div>
</div>
</div>
);
};
By the way, do not struggle with the example that is given on Redux's documentation. I think it is a little bit complex for newcomers.

Related

react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV(...) is not a function

I created a react-redux project and I used json-server as a server. when I create an order, I save status in state in UiReducer and use it in "OrderStatusPage". The NODE_ENV is set to "development". The order is added to my db.json but I got this error in "OrderStatusPage":
Uncaught TypeError: react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_4__.jsxDEV(...) is not a function
how can I solve this error? Thanks a lot.
import React from "react";
import { useSelector } from "react-redux";
export const OrderStatusPage = () => {
const notification = useSelector((state) => state.ui.notification);
return (
<div className="container">
<div className="row d-flex justify-content-center">
<div className="col-md-6 my-5 text-center">
{notification &&
(<Notification
title={notification.title}
message={notification.message}
status={notification.status}
/>)(notification.status === "success") ? (
<Button type="button" >
Go to your Order
</Button>
) : (
<Button type="button" >
Go to your Cart
</Button>
)}
</div>
</div>
</div>
);
};
I think everything is fine. Maybe you just need split the two conditions you have like this:
{notification &&
(<Notification
title={notification.title}
message={notification.message}
status={notification.status}
/>)}
{notification.status === "success" ? (
<Button type="button" >
Go to your Order
</Button>
) : (
<Button type="button" >
Go to your Cart
</Button>
)}
I think this will work.
You should not change the state directly.
Try useState like:
import React from "react";
import { useSelector } from "react-redux";
export const OrderStatusPage = () => {
const [uistate, setUistate]=React.useState()
React.useEffect(()=>{
setUistate(useSelector((state) => state.ui.notification));
},[])
return (
<div className="container">
<div className="row d-flex justify-content-center">
<div className="col-md-6 my-5 text-center">
{uistate &&
(<Notification
title={uistate.title}
message={uistate.message}
status={uistate.status}
/>)(uistate.status === "success") ? (
<Button type="button" >
Go to your Order
</Button>
) : (
<Button type="button" >
Go to your Cart
</Button>
)}
</div>
</div>
</div>
);
};
in your code, you are using the Notification and Button components but not importing these components.
import React from "react";
import { useSelector } from "react-redux";
import { Notification } from "./Notification";
import { Button } from "./Button";
export const OrderStatusPage = () => {
const notification = useSelector((state) => state.ui.notification);
return (
<div className="container">
<div className="row d-flex justify-content-center">
<div className="col-md-6 my-5 text-center">
{notification && (
<Notification
title={notification.title}
message={notification.message}
status={notification.status}
/>
)}
{notification.status === "success" ? (
<Button type="button">Go to your Order</Button>
) : (
<Button type="button">Go to your Cart</Button>
)}
</div>
</div>
</div>
);
};

How to create "Selected tab" in Next.Js?

I am trying to create selected tab in Next.Js.
The User will have the option to search for data it can be Users or Posts, what the user will search for will be selected by clicking on one of the buttons.
Once the user clicks on the button the button will change background to blue.
However I can't make it to work properly, when the User clicks on the button the .Selected class gets added to the button but the button doesn't render the CSS.
import React, { MouseEventHandler, ReactElement, useState } from 'react'
import { PageWithLayout } from '../components/Layouts/LayoutConfig'
import MainLayout from '../components/Layouts/MainLayout'
import style from '../styles/Search.module.css'
const Search: PageWithLayout = () => {
const [searchPosts, setPostsSearch] = useState < String > ();
const setSearchOption = (searchFor: String) => {
let searchOption = '';
if (searchFor == 'POSTS') {
searchOption = 'POSTS';
} else {
searchOption = 'USERS';
let button = document.getElementById('usersOption') as HTMLElement;
button.className += style.Selected;
}
console.log(searchOption);
setPostsSearch(searchOption);
}
return (
<>
<div className='pageContent'>
<div className={style.SearchBarContainer}>
<div className={style.SearchContainer}>
<i className="fa-solid fa-magnifying-glass"></i>
<input className={style.SearchBar} type={'text'} placeholder='Search...' />
</div>
<div className={style.SearchOptions}>
<button id='usersOption' onClick={() => setSearchOption('USERS')}>Users</button>
<button id='postsOption' onClick={() => setSearchOption('POSTS')}>Posts</button>
</div>
</div>
<div className='SearchedContent'>
</div>
</div>
</>
)
}
Search.getLayout = function getLayout(page: ReactElement) {
return (
<MainLayout>
{page}
</MainLayout>
)
}
export default Search
you can use searchOption data for className style
import React, { MouseEventHandler, ReactElement, useState } from 'react'
import { PageWithLayout } from '../components/Layouts/LayoutConfig'
import MainLayout from '../components/Layouts/MainLayout'
import style from '../styles/Search.module.css'
const Search: PageWithLayout = () => {
const [searchPosts, setPostsSearch] = useState<String>();
return (
<>
<div className='pageContent'>
<div className={style.SearchBarContainer}>
<div className={style.SearchContainer}>
<i className="fa-solid fa-magnifying-glass"></i>
<input className={style.SearchBar} type={'text'} placeholder='Search...'/>
</div>
<div className={style.SearchOptions}>
<button id='usersOption' className={searchPosts === 'USERS' ? style.Selected : undefined } onClick={() => setPostsSearch('USERS')}>Users</button>
<button id='postsOption' className={searchPosts === 'POSTS' ? style.Selected : undefined } onClick={() => setPostsSearch('POSTS')}>Posts</button>
</div>
</div>
<div className='SearchedContent'>
</div>
</div>
</>
)
}
Search.getLayout = function getLayout(page: ReactElement){
return(
<MainLayout>
{page}
</MainLayout>
)
}
export default Search
Just have a state for active searchOption and apply the class conditionally directly into the JSX.
const [activeSearchOption, setActiveSearchOption] = useState('USERS')
return (
<>
<div className='pageContent'>
<div className={style.SearchBarContainer}>
<div className={style.SearchContainer}>
<i className="fa-solid fa-magnifying-glass"></i>
<input className={style.SearchBar} type={'text'} placeholder='Search...'/>
</div>
<div className={style.SearchOptions}>
<button id='usersOption' className={activeSearchOption === 'USERS' ? 'active' : ''} onClick={() => setSearchOption('USERS')}>Users</button>
<button id='postsOption' className={activeSearchOption === 'POSTS' ? 'active' : ''} onClick={() => setSearchOption('POSTS')}>Posts</button>
</div>
</div>
<div className='SearchedContent'>
</div>
</div>
</>
)

reload or re render react child component

i got trouble making a search function in react, not the function itself but how it redirecting. before i've tried using redirect and it doesn't load the parent component. no solution, then i changes the logic using Link to instead of redirect to. now the problem is the child component doesnt re-render and the only thing changes is the url.
Here is the complete code.
Child component:
class Search extends Component {
state = {
products: [],
count: '',
}
componentDidMount() {
window.scrollTo(0, 0)
const { match: { params } } = this.props;
axios.get('http://localhost:8000/api/v1/cari/' + params.userId)
.then(response => {
this.setState({ products: response.data.data, count: response.data.jumlah });
})
}
componentWillReceiveProps(props) {
this.forceUpdate();
this.setState({ diCari: this.state.diCari });
}
render() {
var jumlah = <div className="judul cari">Menampilkan {this.state.count} Produk</div>;
var { products } = this.state;
var hasil = products.map(products => {
<div className="kotakproduk produkcari" />
})
return (
<div>
<div className="gambarproduk">
<img src="https://www.mobiledokan.co/wp-content/uploads/2019/09/Xiaomi-Mi-9-Pro-Dream-White.jpg" />
</div>
<div className="nama">
{products.merk} {products.tipe}
</div>
<div className="harga">
<NumberFormat value={products.harga} displayType={'text'} thousandSeparator={true} prefix={'Rp. '} />
</div>
</div>
)
}
}
Parent component
class Master extends Component {
state = {
cari: '',
diCari: false
};
handleChange1 = (e) => {
this.setState({
cari: e.target.value
})
}
render() {
return (
<div>
<div className="header">
<Link to="/home">
<div className="logo">
<img src="/img/tokopon2.png" />
</div>
</Link>
<input type="text" name="search" placeholder="Search.." onChange={this.handleChange1} />
<Link to={"/search/" + this.state.cari}><button type="submit"><i className="fa fa-search"></i></button></Link>
<div className="login-button">
<Link to="/login">Login</Link>
<div className="keranjang-mobile">
<a href="#">
<span className="glyphicon glyphicon-shopping-cart"></span>
</a>
</div>
<div className="keranjang">
<a href="#" className="btn btn-info btn-lg">
<span className="glyphicon glyphicon-shopping-cart"></span> Keranjang Belanja
</a>
</div>
</div>
</div>
</div>
)
}
}
i already tried history.push still no changes

passing index through react components

I'm studying Reactjs and I'm building a tasks project (CRUD) but I'm stuck at the point of editing, the editing part is in another component and I'm not able to send the index of the task that will be edit, I read the documentation but I'm not capable to make it, please if someone can see my code and tell what I'm doing wrong.
the app (main)code
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
// data
import { todos2 } from './todos.json';
// subcomponents
import TodoForm from './components/TodoForm';
import TodoFormEdit from './components/TodoFormEdit';
class App extends Component {
constructor() {
super();
this.state = {
todos2, mode:'view'
}
this.handleAddTodo = this.handleAddTodo.bind(this);
this.handleEdit2 = this.handleEdit2.bind(this);
}
removeTodo(index) {
this.setState({
todos2: this.state.todos2.filter((e, i) => {
return i !== index
})
});
}
handleAddTodo(todo) {
this.setState({
todos2: [...this.state.todos2, todo]
})
}
handleEdit2(i) {
this.setState({mode: 'edit'});
//const mode = mode === 'edit';
alert(i);
/* alert(this.state.todos2[i].title);
alert(this.state.todos2[i].priority);
alert(this.state.todos2[i].description);
alert(this.state.todos2[i].language);*/
}
render() {
const todosAll = this.state.todos2.map((todo, i) => {
return (
<div className="col-md-4" key={i}>
<div className="card mt-4">
<div className="card-title text-center">
<h3>{todo.title} - { i } </h3>
<span className="badge badge-pill badge-danger ml-2">
{todo.priority}
</span>
</div>
<div className="card-body">
<div>
{todo.description}
</div>
<div>
{todo.language}
</div>
</div>
<div className="card-footer">
<button
className="btn btn-danger"
onClick={this.removeTodo.bind(this, i)}>
Delete
</button>
<button
className="btn btn-warning ml-2"
onClick={this.handleEdit2.bind(this, i)}>
Edit
</button>
</div>
</div>
</div>
)
});
return (
<div className="App">
<nav className="navbar navbar-dark bg-dark">
<a className="navbar-brand" href="/">
Tasks
<span className="badge badge-pill badge-light ml-2">
{this.state.todos2.length}
</span>
</a>
</nav>
<div className="container">
<div className="row mt-4">
<div className="col-md-4 text-center">
<img src={logo} className="App-logo" alt="logo" />
{/* <TodoForm onAddTodo={this.handleAddTodo} ></TodoForm> */ }
{this.state.mode === 'view' ? (
<TodoForm onAddTodo={this.handleAddTodo} />
) : (
<TodoFormEdit index={this.state.i}/>
)}
</div>
<div className="col-md-8">
<div className="row">
{todosAll}
</div>
</div>
</div>
</div>
</div>
)
}
}
export default App;
and the Edit component:
import React, { Component } from 'react';
// data
import { todos2 } from '../todos.json';
class TodoFormEdit extends Component {
constructor (i) {
super(i);
this.state = {
todos2
};
}
render() {
return (
<div>
{this.state.todos2[0].title}
</div>
)
}
}
export default TodoFormEdit;
You're passing this.state.i:
<TodoFormEdit index={this.state.i}/>
It's not clear where you set it–I see mode and todos2 state properties, I don't see i anywhere.

How to sort data and display it in reactjs?

I want to display projects in sorted manner when user clicks on sort by funds then it should display the projects in sorted by funds but my code is not working why so ? I am importing the sortBy() function and using it when user clicks on the button.
home.js:
import React, { Component } from 'react';
import Card from '../common/card';
import Projects from '../../data/projects';
import { sortBy } from './helper';
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
projects: Projects
}
}
render() {
return (
<div>
<div className="header">
<div className="buttonContainer">
<div>
<button className="btn btn-primary mycustom dropdown-toggle mr-4" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">Sort by </button>
<div className="dropdown-menu">
<a className="dropdown-item" href="#" onClick={() => sortBy('funded')}>Percentage fund</a>
<a className="dropdown-item" href="#" onClick={() => sortBy('backers')}>Number of backers</a>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row">
{this.state.projects.map( (val,index) => (
<div class="col-3">
<Card title={val.title} by={val.by} blurb={val.blurb}
url={val.url} funded={val.funded} backers={val.backers} imgurl={index}/>
</div>
))}
</div>
</div>
</div>
)
}
}
helper.js:
import projects from '../../data/projects';
export function sortBy (searchTerm) {
if(searchTerm === 'funded'){
return projects.sort((a,b) => a.funded - b.funded);
}else if(searchTerm === 'backers'){
return projects.sort((a,b) => a.backers - b.backers);
}
}
projects.js:
http://jsfiddle.net/0z8xcf1o/
In your render function, you should iterate over this.state.projects instead of Projects
You need to update your state on every sort
Your sortBy function can be simplified, it should set the state for you, and it would be better to add it to the component:
-
sortBy(searchTerm) {
this.setState({ projects: [...Projects].sort((a, b) => a[searchTerm] - b[searchTerm]) });
}
and then call it with
onClick={() => this.sortBy('funded')}

Categories

Resources