How to sort data and display it in reactjs? - javascript

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')}

Related

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>
</>
)

Data not refreshing after login to homepage in reactjs

I'm saving userdata to localStorage in login component and then redirecting to the homepage. In homepage username is not updating on first visit. I have to reload the page. Then data binds to page after refresh. Please help how can I show data on first visit?
below is my homepage code
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
export default class Header extends Component {
constructor(props) {
super(props);
this.state = {
isLogin: false,
isLogout: false,
user: ""
};
}
componentDidMount() {
const userData = localStorage.getItem("userData");
const user = JSON.parse(userData);
this.setState({ user: user });
if (userData) {
this.setState({ isLogin: true });
}
console.log(userData);
console.log(user);
}
logout = e => {
e.preventDefault();
localStorage.clear();
this.setState({ isLogout: true });
};
render() {
if (this.state.isLogin === false || this.state.isLogout === true) {
return (
<header
id="kr-header"
className="kr-header cd-auto-hide-header kr-haslayout"
>
<div className="container">
<div className="row">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<strong className="kr-logo">
<Link to="/">
<img src="images/logo.png" alt="company logo here" />
</Link>
</strong>
<nav className="kr-addnav">
<ul>
<li>
<Link
id="kr-btnsignin"
className="kr-btn kr-btnblue"
to="login_register"
>
<i className="icon-smiling-face" />
<span>Join Now</span>
</Link>
</li>
<li>
<a
className="kr-btn kr-btngreen"
href="dashboardaddlisting.html"
>
<i className="icon-plus" />
<span>Add Listing</span>
</a>
</li>
</ul>
</nav>
<nav id="kr-nav" className="kr-nav">
<div className="navbar-header">
<button
type="button"
className="navbar-toggle collapsed"
data-toggle="collapse"
data-target="#kr-navigation"
aria-expanded="false"
>
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar" />
<span className="icon-bar" />
<span className="icon-bar" />
</button>
</div>
<div
id="kr-navigation"
className="collapse navbar-collapse kr-navigation"
>
<ul>
<li>
Dasboard
</li>
</ul>
</div>
</nav>
</div>
</div>
</div>
</header>
);
} else {
return (
<header
id="kr-header"
className="kr-header cd-auto-hide-header kr-haslayout"
>
<div className="container">
<div className="row">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<strong className="kr-logo">
<Link to="/">
<img src="images/logo.png" alt="company logo here" />
</Link>
</strong>
<nav className="kr-addnav">
<ul>
<li>
<Link
id="kr-btnsignin"
className="kr-btn kr-btnblue"
to="login_register"
>
<i className="icon-smiling-face" />
<span>{this.state.user.user.firstname}</span>
</Link>
</li>
<li>
<a
className="kr-btn kr-btngreen"
href="dashboardaddlisting.html"
>
<i className="icon-plus" />
<span>Add Listing</span>
</a>
</li>
<li>
<a onClick={this.logout} className="kr-btn kr-btngreen">
<i className="icon-plus" />
<span>Logout</span>
</a>
</li>
</ul>
</nav>
<nav id="kr-nav" className="kr-nav">
<div className="navbar-header">
<button
type="button"
className="navbar-toggle collapsed"
data-toggle="collapse"
data-target="#kr-navigation"
aria-expanded="false"
>
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar" />
<span className="icon-bar" />
<span className="icon-bar" />
</button>
</div>
<div
id="kr-navigation"
className="collapse navbar-collapse kr-navigation"
>
<ul>
<li>
Dasboard
</li>
</ul>
</div>
</nav>
</div>
</div>
</div>
</header>
);
}
}
}
Below is login-register component code
import React, {Component} from 'react';
import { Link,Redirect ,withRouter } from 'react-router-dom';
import PropTypes from "prop-types";
import Otp from './otp';
import axios from '../api';
export default class LoginRegister extends Component {
static contextTypes = {
router: PropTypes.object
}
constructor(props,context){
super(props,context);
this.state = {
fname:'',
lname:'',
emailaddress:'',
password:'',
mobile:'',
user:'',
login_pass:'',
isLogin:false
}
this.regi_data = this.regi_data.bind(this);
this.login_data = this.login_data.bind(this);
// this.otpModalRef = React.createRef();
}
regi_data(e){
this.setState({[e.target.name] : e.target.value}
);
}
login_data(e){
this.setState({[e.target.name] : e.target.value})
}
// otpModalRef = ({onOpenModal}) => {
// this.showModal = onOpenModal;
// }
componentDidMount(){
if (localStorage.getItem('userData')) {
this.context.router.history.push({
pathname:'/',
});
}
}
login = (e) => {
e.preventDefault();
axios.post('/api/signin', {
user:this.state.user,
password:this.state.login_pass,
})
.then(res => {
//console.log(res);
localStorage.setItem('userData', JSON.stringify(res.data));
this.context.router.history.push({
pathname:'/',
});
// window.location.reload();
this.setState({isLogin: true});
})
.catch(function (error) {
console.log(error.message);
})
}
register = (e) => {
e.preventDefault();
axios.post('/api/user/add', {
firstname: this.state.fname,
lastname:this.state.lname,
email:this.state.emailaddress,
password:this.state.password,
mobile:this.state.mobile
},
)
.then(res => {
console.log(res);
// this.showModal();
this.context.router.history.push({
pathname:'/otp_validate',
});
}).catch(function(error){
alert(error.message)
})
}
ISSUE
From the code you have shown above and the issue you are facing, it looks like you have a common component Header that is rendered from the parent component of Login and HomePage, probably from the central App component where you must have also declared the routes for Login and Homepage. If this is the case, the issue you are facing is that when the App loads for the first time, the Header also loads at that time and its componentDidMount method gets called. But since you are not logged in at this time, the header component does not get the user data needed to show the username. Later whenever you perform the actual log in action, store the data in localstorage and redirect to homepage, the header does not get unmounted and remounted because it is outside the scope of these individual Login and Homepage components, so it's componentDidMount event will not be triggered and there will not be any change detected in the header component.
FIX
Approach 1: Either create two different Header Components, one for logged in state and one for logged out state and place them inside the render methods of Login and HomePage components respectively. In this case, the above localstorage logic written in componentDidMount of these Header components shall work properly.
Approach 2: Lift up the user data to the parent of Header component and pass the user data as a prop to this component. In this case you can directly use the property in your Header's render method.
try like this in login component
login = (e) => {
e.preventDefault();
axios.post('/api/signin', {
user:this.state.user,
password:this.state.login_pass,
})
.then(res => {
localStorage.setItem('userData', JSON.stringify(res.data));
this.context.router.history.push({
pathname:'/',
state: { userData: JSON.stringify(res.data) }
});
this.setState({isLogin: true});
})
.catch(function (error) {
console.log(error.message);
})
}
and in homepage check props in componentDidMount
componentDidMount() {
const { userData } = this.props.location.state
// const user = JSON.parse(userData);
this.setState({ user: userData });
if (userData) {
this.setState({ isLogin: true });
}
console.log(userData);
console.log(user);
}
Here you are passing props to home page after login. It should work properly. If not please ask
Write these two lines at top of render() method. Like this:
render() {
const userData = localStorage.getItem("userData");
const user = JSON.parse(userData);
if (user) {
return (...); // logged in ui
} else {
return (...); // logged out ui
}
}
componentDidMount() {
const userData = localStorage.getItem("userData");
const user = JSON.parse(userData);
this.setState({ user: user });
if (userData) {
this.setState({ isLogin: true });
}
console.log(userData);
console.log(user);
this.setState({})
}
Try this approach.
login = (e) => {
e.preventDefault();
axios.post('/api/signin', {
user:this.state.user,
password:this.state.login_pass,
})
.then(res => {
localStorage.setItem('userData', JSON.stringify(res.data));
// delay the redirection after udpated the local storage.
setTimeout(() => {
this.context.router.history.push({
pathname:'/',
});
this.setState({isLogin: true});
}, 500);
})
.catch(function (error) {
console.log(error.message);
})
}
In your header component, you are deriving the data to display from two sources of truth. LocalStorage and Components state.
This will cause problems because now you have to make sure that the two sources are in sync, which is the problem you are facing currently.
If I look at your Header component, you are deriving the state from LocalStorage, so if we can get rid of the use of state react would always try to render your header component and you would avoid your problem of trying to keep the two sources of data in sync.
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
const Header = (props) => {
let userData = localStorage.getItem("userData");
if (userData) { // i.e. user IS logged in
let user = JSON.parse(userData);
return ( /* Your code for showing user data. in logout link onClick, clear the local storage */ )
} else {
return ( /*Your login/register header*/)
}
}
export default Header;
If you are worried about the performance, first measure the impact. If your userData is not a deeply nested huge json, odds are performance overhead will be negligible. Remember, React calling the render method does not mean it will paint the dom.
One assumption I am making: You can rely on LocalStorage as the single source of truth. Ideally I would advice on having some cache invalidation logic in there, but it really depends on your usecase and other security measures you have in place.
I believe it's running so fast that when you redirect the user to the homepage, after the login, the userData was not written to localStorage yet.
So you need to check first if the data was written before the redirect.
const asyncLocalStorage = {
setItem: async function (key, value) {
await null
return localStorage.setItem(key, value)
},
getItem: async function (key) {
await null
return localStorage.getItem(key)
}
}
asyncLocalStorage.setItem('user', 'data')
.then( () => asyncLocalStorage.getItem('user') )
.then( data => {
console.log('User', data)
// Redirect ...
} )
Dude your problem is that you have 3 flags that pretty much do the same and you're handling them the wrong way.
for example, this line
if (this.state.isLogin === false || this.state.isLogout === true)
will is wrong from the get-go, you initialize both flags as false, so you'll go straight to the else condition.
look at this other line right here
if (userData) {
this.setState({ isLogin: true });
}
this code never resets the isLogout flag, and the logout method also has issues
logout = e => {
e.preventDefault();
localStorage.clear();
this.setState({ isLogout: true });
};
if you login then isLogin becomes true and isLogout stays false.
if you logout then isLogout becomes true and isLogin stays true!
at the end of the day, if you don't have userdata, aka your user is null, then you're logged out, no matter how many booleans say the opposite, you have a redundancy of logic issue and you need to simplify your app.
setState on componentWillMount
everything is same as in your componentDidMount, but place it inside of the componentWillMount
If you are using Header component independent of login and home component, they you should use getDerivedStateFromProps(props) instead of componentDidMount, as componentDidMount is called only after initial render.
You can use getDerivedStateFromProps(props, state) life cycle component as it executes before the initial render and also for each re-rendering. The componentDidMount() life cycle method is called after the render method got executed, that too only after the initial render. So setting the state here will be reflected after the component got re-rendered. But the getDerivedStateFromProps is called before the render method is called. You may check the condition there, if there are no changes just return null otherwise update the state there. In getDerived state from props, you may set the state by returning an object. the setState function won't work here, since it is a static method. kindly refer this link https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops
Use the code as like below
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
export default class Header extends Component {
constructor(props) {
super(props);
this.state = {
isLogin: false,
isLogout: false,
user: {}
};
}
static getDerivedStateFromProps(props, state){
const userData = localStorage.getItem("userData");
const user = JSON.parse(userData);
if (state.user !== userData){
return {
user: user,
isLogin: true
}
}
return null;
}
logout = e => {
e.preventDefault();
localStorage.clear();
this.setState({ isLogout: true, isLogin: false });
};
render() {
if (this.state.isLogin === false || this.state.isLogout === true) {
return (
<header
id="kr-header"
className="kr-header cd-auto-hide-header kr-haslayout"
>
<div className="container">
<div className="row">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<strong className="kr-logo">
<Link to="/">
<img src="images/logo.png" alt="company logo here" />
</Link>
</strong>
<nav className="kr-addnav">
<ul>
<li>
<Link
id="kr-btnsignin"
className="kr-btn kr-btnblue"
to="login_register"
>
<i className="icon-smiling-face" />
<span>Join Now</span>
</Link>
</li>
<li>
<a
className="kr-btn kr-btngreen"
href="dashboardaddlisting.html"
>
<i className="icon-plus" />
<span>Add Listing</span>
</a>
</li>
</ul>
</nav>
<nav id="kr-nav" className="kr-nav">
<div className="navbar-header">
<button
type="button"
className="navbar-toggle collapsed"
data-toggle="collapse"
data-target="#kr-navigation"
aria-expanded="false"
>
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar" />
<span className="icon-bar" />
<span className="icon-bar" />
</button>
</div>
<div
id="kr-navigation"
className="collapse navbar-collapse kr-navigation"
>
<ul>
<li>
Dasboard
</li>
</ul>
</div>
</nav>
</div>
</div>
</div>
</header>
);
} else {
return (
<header
id="kr-header"
className="kr-header cd-auto-hide-header kr-haslayout"
>
<div className="container">
<div className="row">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<strong className="kr-logo">
<Link to="/">
<img src="images/logo.png" alt="company logo here" />
</Link>
</strong>
<nav className="kr-addnav">
<ul>
<li>
<Link
id="kr-btnsignin"
className="kr-btn kr-btnblue"
to="login_register"
>
<i className="icon-smiling-face" />
<span>{Object.entries(this.state.user).length > 0 ? this.state.user.user.firstname : `-`}</span>
</Link>
</li>
<li>
<a
className="kr-btn kr-btngreen"
href="dashboardaddlisting.html"
>
<i className="icon-plus" />
<span>Add Listing</span>
</a>
</li>
<li>
<a onClick={this.logout} className="kr-btn kr-btngreen">
<i className="icon-plus" />
<span>Logout</span>
</a>
</li>
</ul>
</nav>
<nav id="kr-nav" className="kr-nav">
<div className="navbar-header">
<button
type="button"
className="navbar-toggle collapsed"
data-toggle="collapse"
data-target="#kr-navigation"
aria-expanded="false"
>
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar" />
<span className="icon-bar" />
<span className="icon-bar" />
</button>
</div>
<div
id="kr-navigation"
className="collapse navbar-collapse kr-navigation"
>
<ul>
<li>
Dasboard
</li>
</ul>
</div>
</nav>
</div>
</div>
</div>
</header>
);
}
}
}

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 Divide React Components into Presentational and Container Components

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.

React Collapse - how do I toggle items in the list?

I am displaying a list of items from database and for each item, I have a button "Show more/less". When this is clicked, I want to show/hide the extra content with a nice slide down/up effect. I have implemented this functionality without the slide down/up effect, but want to use React Collapse to make it more user-friendly.
Here's the component where I am trying to implement the React Collapse functionality:
import React from 'react';
import ReactDOM from 'react-dom';
//import axios from 'axios';
import NewPost from './NewPost';
import {Collapse} from 'react-collapse';
class Posts extends React.Component {
constructor(props) {
super(props);
this.toggleClass= this.toggleClass.bind(this);
this.state = {
activeIndex: null
}
}
toggleClass(index, e) {
this.setState({ activeIndex: this.state.activeIndex === index ? null : index });
};
moreLess(index) {
if (this.state.activeIndex === index) {
return (
<span>
<i className='fas fa-angle-up'></i> Less
</span>
);
} else {
return (
<span>
<i className='fas fa-angle-down'></i> More
</span>
);
}
}
render () {
let content;
if (this.props.loading) {
content = 'Loading...';
} else {
content = this.props.posts.map((post, key) => {
return(
<li key={key}>
<div>
<span>{post.id}</span>
<span>{post.message}</span>
<button className="btn btn-primary btn-xs" onClick={this.toggleClass.bind(this, key)}>
{this.moreLess(key)}
</button>
</div>
<Collapse isOpened={true}>
<div className={'alert alert-info msg '+(this.state.activeIndex === key ? "show" : "hide")}>
{post.message}
</div>
</Collapse>
</li>
)
});
}
return (
<div>
<h1>Posts!</h1>
<div className="row">
<div className="col-md-6">
<ul>
{content}
</ul>
</div>
</div>
</div>
);
}
}
export default Posts
But when I click on the More/less button, the content in Collapse doesn't appear - after clicking the button nothing happens.
What am I missing here yet?
if you're using function and hooks I recommend this
import { Collapse } from "react-collapse";
import classNames from "classnames";
import React, { useState} from 'react';
export default function yourFunction() {
const [activeIndex, setActiveIndex] = useState(null);
return(
{groups.map((group, index) => (
<button className="btn btn-primary navbar-toggler"
type="button"
data-toggle="collapse"
onClick={event => setActiveIndex(
activeIndex === index ? null : index
)}
data-target="#collapseExample"
aria-expanded="false"
aria-controls="collapseExample">
[CLICK HERE]
</button>
<Collapse isOpened={activeIndex === index}>
<div
className={classNames("alert alert-info msg", {
show: activeIndex === index,
hide: activeIndex !== index
})}
>
<a>[YOUR COLLAPSE CONTENT]</a>
</div>
</Collapse>
)
}
You didn't bind correct check to <Collapse isOpened={true}>. Instead of true, you should put (this.state.)activeIndex === index (current item index) like this:
<Collapse isOpened={this.state.activeIndex === index}>
So it can actually collapse due to activeIndex. I've made codesandbox for you so you can make sure it works: https://codesandbox.io/s/jzx44ynyqw
But I think this is the most important part of it (note that your index was called key, I just renamed it for convenience):
<li key={index}>
<div>
<p>{post.title}</p>
<Collapse isOpened={activeIndex === index}>
<div
className={classNames("alert alert-info msg", {
show: activeIndex === index,
hide: activeIndex !== index
})}
>
{post.message}
</div>
</Collapse>
<button
className="btn btn-primary btn-xs"
onClick={this.toggleClass.bind(this, index)}
>
{this.moreLess(index)}
</button>
</div>
</li>

Categories

Resources