I'm managing Todo lists in my app. The main view is a page with all the lists displayed as cards. If you click on one of them, you can modify, update, delete stuff through a modal that appears.
I have a TodoLists reducer that store all the TodoLists. I don't know how to handle the modal. Should I use redux or just local state?
import _ from "lodash";
import React from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { listsActions } from "../duck";
import NewList from "./NewList";
import Card from "./Card";
import Modal from "./Modal";
class Lists extends React.Component {
constructor(props) {
super(props);
this.state = {
modal: false,
list: {}
};
this.hideModal = this.hideModal.bind(this);
this.renderModal = this.renderModal.bind(this);
}
componentDidMount() {
const { fetchByUserId, user } = this.props;
if (user !== undefined) {
fetchByUserId(user.id);
}
}
hideModal() {
this.setState({
modal: false
});
}
renderModal() {
this.setState({
modal: true
});
}
render() {
const { items } = this.props;
const { modal, list } = this.state;
return (
<div>
<NewProject />
<div className="columns">
{_.map(items, (l) => (
<div
key={l.id}
className="column"
>
<Card
list={l}
onClick={() => this.renderModal(l)}
/>
</div>
))}
</div>
<Modal
className={modal ? "is-active" : ""}
list={list}
onClose={this.hideModal}
/>
</div>
);
}
}
const mapStateToProps = (state) => {
const { user } = state.authentication;
const { items, loading, error } = state.lists;
return {
user,
items,
loading,
error
};
};
export default connect(
mapStateToProps,
{ fetchByUserId: listsActions.fetchByUserId }
)(Projects);
Related
I have a react app with a large menu, and as such am trying to move it to a seperate file from the main app.js
at the mement when you click on a link in the menu it call a node api and which returns some data, however when I try to seperate I can not get it to populate the results section which is still in the main script
Working version app.js
import React,{ useState } from 'react';
import './App.css';
import axios from 'axios';
import { Navigation } from "react-minimal-side-navigation";
import "react-minimal-side-navigation/lib/ReactMinimalSideNavigation.css";
export default class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
result: [],
};
this.callmyapi = this.callmyapi.bind(this);
}
render() {
return (
<div>
<div class="menu">
<Navigation
onSelect={({itemId}) => {
axios.get(`/api/menu/`, {
params: {
Menu: itemId,
}
})
.then(res => {
const results = res.data;
this.setState({ results });
})
.catch((err) => {
console.log(err);
})
}}
items={[
{
title: 'Pizza',
itemId: '/menu/Pizza/',
},
{
title: 'Cheese',
itemId: '/menu/cheese',
}
]}
/>
</div>
<div class="body">
this.state.results && this.state.results.map(results => <li>* {results.Name}</li>);
</div>
</div>
);
}
}
New app.js
import React,{ useState } from 'react';
import './App.css';
//import axios from 'axios';
//import { Navigation } from "react-minimal-side-navigation";
//import "react-minimal-side-navigation/lib/ReactMinimalSideNavigation.css";
import MyMenu from './mymenu';
export default class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
result: [],
};
this.callmyapi = this.callmyapi.bind(this);
}
render() {
return (
<div>
<div class="menu">
<MyMenu />
</div>
<div class="body">
this.state.results && this.state.results.map(results => <li>* {results.Name}</li>);
</div>
</div>
);
}
}
New menu file
mymenu.js
import React, { Component } from 'react';
import axios from 'axios';
import './App.css';
//import MyList from './App.js';
//import { ProSidebar, Menu, MenuItem, SubMenu } from 'react-pro-sidebar';
//import 'react-pro-sidebar/dist/css/styles.css';
import { Navigation } from "react-minimal-side-navigation";
//import Icon from "awesome-react-icons";
import "react-minimal-side-navigation/lib/ReactMinimalSideNavigation.css";
//export default async function MyMenu(){
export default class MyMenu extends React.Component {
constructor(props) {
super(props);
};
render() {
return (
<div>
<Navigation
// you can use your own router's api to get pathname
activeItemId="/management/members"
onSelect={({itemId}) => {
// return axios
axios.get(`/api/menu/`, {
params: {
// Menu: itemId,
Menu: "meat",
SubMenu : "burgers"
}
})
.then(res => {
const results = res.data;
this.setState({ results });
})
.catch((err) => {
console.log(err);
})
}}
items={[
{
title: 'Pizza',
itemId: '/menu/Pizza/',
},
{
title: 'Cheese',
itemId: '/menu/cheese',
}
]}
/>
</div>
);
}
}
Any help would be greatly appreciated
That one is quite easy once you understand state. State is component specific it that case. this.state refers to you App-Component and your Menu-Component individually. So in order for them to share one state you have to pass it down the component tree like this.
export default class MyList extends React.Component {
constructor(props) {
super(props);
this.state = {
result: [],
};
}
render() {
return (
<div>
<div class="menu">
<MyMenu handleStateChange={(results: any[]) => this.setState(results)} />
</div>
<div class="body">
this.state.results && this.state.results.map(results => <li>* {results.Name}</li>);
</div>
</div>
);
}
}
See this line: <MyMenu handleStateChange={(results: any[]) => this.setState(results)} />
There you pass a function to mutate the state of App-Component down to a the child
There you can call:
onSelect={({itemId}) => {
// return axios
axios.get(`/api/menu/`, {
params: {
// Menu: itemId,
Menu: "meat",
SubMenu : "burgers"
}
})
.then(res => {
const results = res.data;
this.props.handleStateChange(results)
})
.catch((err) => {
console.log(err);
})
You mutate the parent state and the correct data is being rendered. Make sure to practice state and how it works and how usefull patterns look like to share state between components.
Thanks - I Have found solution (also deleted link question)
above render added function
handleCallback = (results) =>{
this.setState({data: results})
}
then where I display the menu
<MyMenu parentCallback = {this.handleCallback}/>
where i display the results
{this.state.results && this.state.results.map(results => <li>{results.Name}</li>}
No aditional changes to the menu scripts
As we know, the structure of a class component can be simplified as the following:
// Blank 1
class Books extends Component {
// Blank 2
render(){
// Blank 3
return()
}
export default Books;
So just for example:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { updateFilters } from '../../../services/filters/actions';
import Checkbox from '../../Checkbox';
import GithubStarButton from '../../github/StarButton';
import './style.scss';
const availableSizes = ['XS', 'S', 'M', 'ML', 'L', 'XL', 'XXL'];
class Filter extends Component {
static propTypes = {
updateFilters: PropTypes.func.isRequired,
filters: PropTypes.array
};
componentWillMount() {
this.selectedCheckboxes = new Set();
}
toggleCheckbox = label => {
if (this.selectedCheckboxes.has(label)) {
this.selectedCheckboxes.delete(label);
} else {
this.selectedCheckboxes.add(label);
}
this.props.updateFilters(Array.from(this.selectedCheckboxes));
};
createCheckbox = label => (
<Checkbox
classes="filters-available-size"
label={label}
handleCheckboxChange={this.toggleCheckbox}
key={label}
/>
);
createCheckboxes = () => availableSizes.map(this.createCheckbox);
render() {
return (
<div className="filters">
<h4 className="title">Sizes:</h4>
{this.createCheckboxes()}
<GithubStarButton />
</div>
);
}
}
const mapStateToProps = state => ({
filters: state.filters.items
});
export default connect(
mapStateToProps,
{ updateFilters }
)(Filter);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { fetchProducts } from '../../services/shelf/actions';
import { addProduct } from '../../services/cart/actions';
import Product from './Product';
import Filter from './Filter';
import ShelfHeader from './ShelfHeader';
import Clearfix from '../Clearfix';
import Spinner from '../Spinner';
import './style.scss';
class Shelf extends Component {
static propTypes = {
fetchProducts: PropTypes.func.isRequired,
products: PropTypes.array.isRequired,
addProduct: PropTypes.func.isRequired,
filters: PropTypes.array,
sort: PropTypes.string
};
state = {
loading: false
};
componentWillMount() {
const { filters, sort } = this.props;
this.handleFetchProducts(filters, sort);
}
componentWillReceiveProps(nextProps) {
const { filters: nextFilters, sort: nextSort } = nextProps;
if (nextFilters !== this.props.filters) {
this.handleFetchProducts(nextFilters, undefined);
}
if (nextSort !== this.props.sort) {
this.handleFetchProducts(undefined, nextSort);
}
}
handleFetchProducts = (
filters = this.props.filters,
sort = this.props.sort
) => {
this.setState({ loading: true });
this.props.fetchProducts(filters, sort, () => {
this.setState({ loading: false });
});
};
render() {
const { products } = this.props;
const p = products.map(p => {
return (
<Product product={p} addProduct={this.props.addProduct} key=
{p.id} />
);
});
return (
<React.Fragment>
{this.state.loading && <Spinner />}
<Filter />
<div className="shelf-container">
<ShelfHeader productsLength={products.length} />
{p}
<Clearfix />
</div>
<Clearfix />
</React.Fragment>
);
}
}
const mapStateToProps = state => ({
products: state.shelf.products,
filters: state.filters.items,
sort: state.sort.type
});
export default connect(
mapStateToProps,
{ fetchProducts, addProduct }
)(Shelf);
Except for state and life cycle methods, sometimes we define other types of attributes and functions in Blank 1, sometimes in Blank 2, sometimes in Blank 3. So I am wondering when we are going to define attributes and functions, which part should we choose? Is there a convention or something like that?
Block 1 is for defining variables and functions which are not depended on component ,these are general variables and functions which could be used in the component and can even be exported in another files.
Block 2 is for defining component specific variables and methods, define lifecycle methods.variables and methods defined in block 2 could be accessed using this keyword.
Block 3 is used when we want to execute certain piece of code,every time when render method is executed.Apart from initial render, render method is executed every time when setState is performed,so avoid writing code in block 3 as it's excessive.
Hope this helps,
Cheers !!
I'm using react antd table with pagination,sort & etc. it'll fetch data when component is loaded. when te user clicks on pagination it should call Api and get new data accordingly. it's getting bada but the table won't update with new data.
I'm new with react. please help me with this,
import PropTypes from 'prop-types';
import React, { Component } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from 'redux';
import { Row, Col } from "antd";
import clone from "clone";
import Button from "../../components/uielements/button";
import PageHeader from "../../components/utility/pageHeader";
import Box from "../../components/utility/box";
import LayoutWrapper from "../../components/utility/layoutWrapper";
import SignUpForm from "../../components/vendorSignup";
import basicStyle from "../../settings/basicStyle";
import actions from "../../redux/vendorSignUp/actions";
import { createColumns } from "./config";
import { ButtonWrapper } from "../../components/card/cardModal.style";
import SimpleTable from "./antTables/tableViews/sortView";
import ContentHolder from '../../components/utility/contentHolder';
import Spin from '../Feedback/Spin/spin.style';
const { addVendor, getVendors } = actions;
class Cards extends Component {
static propTypes = {
dispatch: PropTypes.func
};
constructor(props) {
super(props);
this.addColumn = this.addColumn.bind(this);
this.editColumn = this.editColumn.bind(this);
this.handleCancel = this.handleCancel.bind(this);
this.submitCard = this.submitCard.bind(this);
this.updateCard = this.updateCard.bind(this);
this.tableInfo = {
title: "Sort Table",
value: "sortView",
columns: []
};
this.tableInfo.columns = createColumns(this.editColumn);
this.state = {
editView: false,
selectedCard: null,
modalType: ""
};
}
componentWillMount() {
const { getVendors } = this.props.actions;
}
render() {
const style = {
textAlign: 'center',
background: '#f1f3f6',
padding: '30px 50px'
};
const { rowStyle, colStyle, gutter } = basicStyle;
const { editView, selectedCard, modalType } = this.state;
const vendorSignUp = clone(this.props.vendorSignUp);
if (vendorSignUp.length == 0) {
return (<LayoutWrapper>
<PageHeader>Vendors</PageHeader>
<ContentHolder>
<div style={style}>
<Spin spinning={vendorSignUp.length === 0} />
</div>
</ContentHolder>
</LayoutWrapper>);
}
return (
<LayoutWrapper>
<PageHeader>Vendors</PageHeader>
<Row style={rowStyle} gutter={gutter} justify="start">
<Col md={24} sm={24} xs={24} style={colStyle}>
<Box>
<ButtonWrapper className="isoButtonWrapper">
<Button type="primary" className="" onClick={this.addColumn}>
Add New Vendor
</Button>
</ButtonWrapper>
<SimpleTable columns={this.tableInfo.columns} dataSource={vendorSignUp} loading={this.loading} onChange={this.onChange} />
{selectedCard ? (
<SignUpForm
saveFormRef={this.saveFormRef}
editView={editView}
modalType={modalType}
onCancel={this.handleCancel}
onCreate={this.submitCard}
onOk={this.submitCard}
wrappedComponentRef={this.saveFormRef}
/>
) : ("")}
</Box>
</Col>
</Row>
</LayoutWrapper>
);
}
}
function mapStateToProps(state) {
return {
...state.vendorSignUp.toJS()
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getVendors, addVendor
}, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Cards);
Here is my table view;
import React, { Component } from 'react';
import { connect } from 'react-redux';
import TableWrapper from '../antTable.style';
import actions from "../../../../redux/vendorSignUp/actions";
const { getVendors } = actions;
class SignIn extends Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.state = {
dataList: this.props.dataSource || this.props.dataList.getAll()
};
this.columns = this.props.columns || this.props.tableInfo.columns;
}
onChange(pagination, filters, sorter) {
if (sorter.order) {
if (sorter.order == "ascend") {
const { getVendors } = this.props;
this.setState({
loading: true,
});
getVendors('3-5');
}
else {
this.props.dataSource.sort(function (a, b) {
var x = a[sorter.field].toLowerCase();
var y = b[sorter.field].toLowerCase();
if (y < x) { return -1; }
if (y > x) { return 1; }
return 0;
});
}
}
}
render() {
return (
<TableWrapper
columns={this.columns}
onChange={this.onChange}
dataSource={this.state.dataList}
className="isoSortingTable"
pagination={{ pageSize: 10 }}
loading={this.state.loading}
/>
);
}
}
function mapStateToProps(state) {
return {
...state
};
}
export default connect(mapStateToProps,
{ getVendors }
)(SignIn);
problem was in the constructor.it's will be executed only one time. therefore this.state.datalist will be the same. that's why it did not work.
I have a course page with a list of students as buttons. On click, the button should render ShowStudentInfo but my ShowStudentInfo component is not rendering on click when nested inside (if state.isClicked). Outside of the the conditional it works fine but I need that conditional otherwise everything will show up on course page render.
Main Course Component
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions/index';
import { Redirect } from 'react-router';
import { Link } from 'react-router-dom';
import ShowStudentInfo from '../ShowStudentInfo/ShowStudentInfo'
class CoursePage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
course: {},
student: {},
isClicked: false
};
console.log(this.props.match.params.cuid)
this.onClick = this.onClick.bind(this)
}
onClick(event) {
event.preventDefault()
console.log(this.state.isClicked)
this.setState({
isClicked: !this.state.isClicked
})
}
componentDidMount() {
this.props.dispatch(actions.getCourse(this.props.match.params.cuid));
this.props.dispatch(actions.getStudents());
}
render() {
let studentList = this.props.student
const students = Object.keys(studentList).map(student => studentList[student])
const currentStudents = students.map(student => {
if ((this.props.match.params.cuid) == student.courses) {
return (
<p>
<button className="students" id={student._id} onClick={this.onClick}>{student.firstName} {student.lastName}</button>
</p>
)
if (this.state.isClicked) {
return (
<div className="student-info">
<ShowStudentInfo firstName={student.firstName}
lastName={student.lastName} phoneNumber={student.phoneNumber} />
</div>
)
}
}
})
return (
<div>
<h1>{this.props.course.name}</h1>
<Link to={`/addStudent/${this.props.match.params.cuid}`}> Add a new student</Link>
<div className="studentList">Your students{currentStudents} </div>
</div>
);
}
}
const mapStateToProps = (state, props) => {
return {
course: state.course.course,
student: state.student.students
}
}
export default connect(mapStateToProps)(CoursePage)
My ShowStudentInfo component
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions/index';
import { Redirect } from 'react-router';
import { Link } from 'react-router-dom';
class ShowStudentInfo extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
student: {
firstName: '',
lastName: '',
phoneNumber: ''
},
isClickedEdit: false,
isClickedDelete: false
}
this.isClickedEdit = this.isClickedEdit.bind(this)
this.isClickedDelete = this.isClickedDelete.bind(this)
}
isClickedEdit(event) {
this.setState({
isClickedEdit: true
})
}
isClickedDelete(event) {
this.setState({
isClickedDelete: true
})
}
render() {
return (
<div className="student-info-container">
<p>Name: {this.props.firstName} {this.props.lastName}</p>
<p>Phone Number: {this.props.phoneNumber}</p>
</div>
)
}
}
if (this.state.isClicked) {
return (
<div className="student-info">
<ShowStudentInfo firstName={student.firstName}
lastName={student.lastName} phoneNumber={student.phoneNumber} />
</div>
)
}
is the most important part to look at and the info is rendering normally without the onClick conditional.
Your map function contains 2 return functions, while the first return will cause it to go to the next item in the current iteration process
if ((this.props.match.params.cuid) == student.courses) {
return (
<p>
<button className="students" id={student._id} onClick={this.onClick}>{student.firstName} {student.lastName}</button>
</p>
)
// this will never hit in case the previous statement is evaluates to true
if (this.state.isClicked) {
return (
<div className="student-info">
<ShowStudentInfo firstName={student.firstName}
lastName={student.lastName} phoneNumber={student.phoneNumber} />
</div>
)
}
}
To achieve what you want to do, (i am guessing you wish it as part of the first return statement), you could do it like so
if ((this.props.match.params.cuid) == student.courses) {
return (
<p>
<button className="students" id={student._id} onClick={this.onClick}>{student.firstName} {student.lastName}</button>
{ this.state.isClicked && <div className="student-info">
<ShowStudentInfo firstName={student.firstName}
lastName={student.lastName} phoneNumber={student.phoneNumber} />
</div> }
</p>
)
}
Be warned: This will show the students info as part of the p tag for all the students on the screen, I don't see where you restrict showing the users info for the one clicked student.
As another side note, are you sure you wish to comparse an object of courses with a cuid parameter?
This is just a little refactoring in your logic :) hope it helps you.
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions/index';
import { Redirect } from 'react-router';
import { Link } from 'react-router-dom';
import ShowStudentInfo from '../ShowStudentInfo/ShowStudentInfo'
class CoursePage extends React.Component {
state = {
course: {},
student: {},
isClicked: false
};
onClick = (event) => {
event.preventDefault()
console.log(this.state.isClicked)
this.setState({
isClicked: !this.state.isClicked
})
}
getCurrentStudents = (students) => {
const { match } = this.props
const { isClicked } = this.state
return students.map(student => {
if (match.params.cuid == student.courses) {
return (
<div>
<p><button className="students" id={student._id} onClick={this.onClick}>
{student.firstName} {student.lastName}
</button></p>
{isClicked && this.getStudentInfo(student) }
</div>
)
}
})
}
getStudentInfo = (student) => (
<div className="student-info" >
<ShowStudentInfo
firstName={student.firstName}
lastName={student.lastName}
phoneNumber={student.phoneNumber} />
</div>
)
componentDidMount() {
let { match, dispatch } = this.props
dispatch(actions.getCourse(match.params.cuid));
dispatch(actions.getStudents());
}
render() {
let { studentList, match, course } = this.props
const students = Object.keys(studentList).map(student => studentList[student])
const currentStudents = this.getCurrentStudents(students)
return (
<div>
<h1>{course.name}</h1>
<Link to={`/addStudent/${match.params.cuid}`}> Add a new student</Link>
<div className="studentList">Your students{currentStudents} </div>
</div>
);
}
}
const mapStateToProps = (state, props) => {
return {
course: state.course.course,
student: state.student.students
}
}
export default connect(mapStateToProps)(CoursePage)
I have a simple Cart component and I want to show either a "Your cart is empty" message when there are no items in it.
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as CartActions from '../actions/cart'
import Shelf from './Shelf'
import EmptyCart from './EmptyCart'
/*
This is a container component
*/
class Cart extends Component {
constructor(props) {
super(props)
this.state = {
itemQuantity: props.cart.length
}
}
render() {
const CartItems = this.props.cart.map(
(item, idx) =><li key={idx}>{item.name} - ${item.price}</li>
)
const isCartEmpty = () => this.state.itemQuantity === 0
console.log("is cart empty? ", isCartEmpty(), "cart item quantity ", this.state.itemQuantity)
return(
<div className="Cart">
<Shelf addItem={this.props.action.addToCart} />
<h2>Cart Items</h2>
<ol>
{ isCartEmpty() ? <EmptyCart/> : {CartItems} }
</ol>
</div>
)
}
}
function mapStateToProps(state, prop) {
return {
cart: state.cart
}
}
function mapDispatchToProps(dispatch) {
return {
action: bindActionCreators(CartActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Cart)
My Shelf component looks like this:
import React, { Component } from 'react';
class Shelf extends Component {
constructor(props) {
super(props)
this.addItemToCart = this.addItemToCart.bind(this)
this.state = {
shelfItems: [
{ "name": 'shampoo', "price": 23 },
{ "name": 'chocolate', "price": 15 },
{ "name": 'yogurt', "price": 10 }
]
}
}
addItemToCart(item){
this.props.addItem(item)
}
render() {
const shelfItems = this.state.shelfItems.map((item, idx) => {
return <li key={idx}><button onClick={()=>this.addItemToCart(item)}>[+]</button>{item.name} - ${item.price}</li>
})
return(
<div>
<h2>Shelf</h2>
<ul>
{shelfItems}
</ul>
</div>
)
}
}
export default Shelf
Cart Reducer:
export default(state = [], payload) => {
switch (payload.type) {
case 'add':
return [...state, payload.item]
default:
return state
}
}
addToCart action:
export const addToCart = (item) => {
return {
type: 'add',
item
}
}
The empty message shows up but the list does not update when I add items. What am I doing wrong? The code works just fine if I remove the conditionals and just render CartItems
It's because you set only initial state. When you add item you don't set a new state. If you use redux there is no local state needed.
Try this:
class Cart extends Component {
constructor(props) {
super(props)
this.state = {}
}
render() {
const CartItems = this.props.cart.map(
(item, idx) =><li key={idx}>{item.name} - ${item.price}</li>
)
const isCartEmpty = CartItems.length === 0
return(
<div className="Cart">
<Shelf addItem={this.props.action.addToCart} />
<h2>Cart Items</h2>
<ol>
{isCartEmpty ? <li>Your Cart is Empty</li> : CartItems}
</ol>
</div>
)
}
}