I have implemented a component that shows courses. Within the same component, there is functionality for filtering courses when a user searches for a certain course.
The problem I am having is when I dispatch an action for filtering courses, the redux store shows the courses state has been updated to contain only courses that match the search query but the component does not reflect the new state(i.e still shows all courses). How can I resolve this issue?
Here is what I have implemented so far
Action
export const retrieveAllCourses = (url, query) => dispatch => {
return axios
.get(url + `?title=${query}`, {
headers: myHeaders
})
.then(res => {
const fetchall = {
type: ACTION_TYPE.VIEW_COURSES,
payload: res.data.courses
};
dispatch(fetchall);
})
.catch(error => {
toast.error(error, { autoClose: 3500, hideProgressBar: true });
});
};
Reducer
import ACTION_TYPE from "../../actions/actionTypes";
const initialState = {
courses: [],
};
const coursesReducer = (state = initialState, action) => {
switch (action.type) {
case ACTION_TYPE.VIEW_COURSES:
return {
...state,
courses: state.courses.concat(action.payload.results),
};
default:
return state;
}
};
export default coursesReducer;
Search component
import React from "react";
import PropTypes from "prop-types";
class Search extends React.Component {
state = {
search: ""
};
handleChange = ev => {
ev.preventDefault();
const { onChange } = this.props;
const search = ev.target.value;
this.setState({ search });
if (onChange) {
onChange(search);
}
};
handleSearch = ev => {
ev.preventDefault();
const { onSearch } = this.props;
const { search } = this.state;
if (onSearch) {
onSearch(search);
}
};
render() {
const { id } = this.props;
window.onload = function() {
var input = document.getElementById("search-input");
input.addEventListener("keyup", function(event) {
if (event.keyCode === 13) {
event.preventDefault();
document.getElementById("mySearchBtn").click();
}
});
};
return (
<React.Fragment>
<form id={id}>
<div className="search-container">
<div className="input-group">
<input
type="text"
id="search-input"
className="search-input form-control"
placeholder="Search lessons"
onChange={this.handleChange}
/>
</div>
<div>
<button id="mySearchBtn" onClick={this.handleSearch} />
</div>
</div>
</form>
</React.Fragment>
);
}
}
Search.propTypes = {
onChange: PropTypes.func,
onSearch: PropTypes.func,
id: PropTypes.number
};
export default Search;
Navbar component
import React from "react";
import PropTypes from "prop-types";
import Search from "../../components/courses/Search";
export const LoggedInNavBar = ({ handleSearch, handleSearchChange}) => {
return (
<div className="row bobo-menu">
<div className="col-sm">
<div className="logo-container">
<a href={"/courses"}>
<h1 id="logo" className="hidden">
TUTORIALS
</h1>
</a>
</div>
</div>
<div className="col-sm">
<Search onSearch={handleSearch} onChange={handleSearchChange} />
</div>
<div className="col-sm">
<p id="motto">
TUTORIALS<span className="text-succes"> AND</span> LEARNING{" "}
<span className="text-succes">FOR ALL</span>
</p>
</div>
<div className="col-sm">
<div className="row row__menu__icons">
<div className="col-lg-3 col-md-3 col-sm-3">
<a
id="title"
href="/create"
className="btn btn-login "
data-mode="signin"
>
{" "}
<i className="fas fa-plus" />
</a>
</div>
<div className="col-lg-3 col-md-3 col-sm-3">
<a
id="title"
href="/create"
className="btn btn-login "
data-mode="signin"
>
<i className="far fa-user" />
</a>
</div>
<div className="col-lg-3 col-md-3 col-sm-3">
<a
id="title"
href="/me/stories"
className="btn btn-login "
data-mode="signin"
>
<i className="fas fa-book" />
</a>
</div>
<div className="col-lg-3 col-md-3 col-sm-3">
<a
id="title"
href="/create"
className="btn btn-login "
data-mode="signin"
>
<i className="fas fa-sign-out-alt" />
</a>
</div>
</div>
</div>
<br />
<br />
</div>
);
};
LoggedInNavBar.propTypes = {
handleSearch: PropTypes.func,
handleSearchChange: PropTypes.func
};
Courses component
import React, { Fragment } from "react";
import { details } from "../../routes/protectedRoutes";
import { AUTHENTICATED } from "../../utils/myHeaders";
import { ViewAllCourses } from "../../views/courses/viewSearchResults";
import { retrieveAllCourses } from "../../actions/courseActions/courseAction";
import { LoggedInNavBar } from "../navigation/LoggedIn";
import { API_URLS } from "../../appUrls";
import PropTypes from "prop-types";
import { connect } from "react-redux";
export class Courses extends React.Component {
constructor(props) {
super(props);
this.user = details(AUTHENTICATED);
this.state = {
search: ""
};
}
componentDidMount() {
this.fetchCourses();
}
fetchCourses = (searchStr = null) => {
let Url = API_URLS.FETCH_CREATE_ARTICLES;
const { search } = this.state;
const query = searchStr !== null ? searchStr : search;
this.props.dispatch(retrieveAllCourses( Url, query ));
};
handleSearch = search => {
this.setState({ search });
this.fetchCourses(search);
};
handleSearchChange = search => {
if (!search) {
this.setState({ search });
this.fetchCourses(search);
}
};
render() {
const { allCourses } = this.props;
return (
<div>
<LoggedInNavBar
handleSearchChange={this.handleSearchChange}
handleSearch={this.handleSearch}
/>
<ViewAllCourses results={allVideos} />
</div>
);
}
}
Courses.propTypes = {
dispatch: PropTypes.func.isRequired,
allCourses: PropTypes.array
};
const mapStateToProps = state => ({
allCourses: state.coursesReducer.courses
});
const mapDispatchToProps = dispatch => ({ dispatch });
export default connect(
mapStateToProps,
mapDispatchToProps
)(Courses);
API response
In your reducer, why are you concatenating it with previous state results? Your filtering will never show associated data if you're doing this.
I see no payload.results in your action dispatch. Shouldn't it be action.payload & not action.payload.results?
return {
...state,
courses: action.payload,
}
There's no state variable called videos in your reducer. You're dispatching & storing courses, so you have to listen to:
const mapStateToProps = state => ({
allCourses: state.coursesReducer.courses
});
Hope this is helpful!
Related
Im already learning about to fetch data from REST API with react, i have to components (form for submit and a card for get data) both summon from parent component (App), and is working, so i got to push new todo to db and get the news values on my card component, but instead only render cards components, render the all App (incluyed the form component), what am i doing wrong guys?
This is the parent Component
import React, { Component } from 'react';
import './App.css';
import SampleCard from './components/SampleCard';
import Form from './components/Form';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: []
}
}
componentDidMount() {
this.getTasks()
}
getTasks =_=> {
fetch('http://localhost:4000/users')
.then(response => response.json())
.then(data => this.setState({ data: data.data }))
.catch(err => console.log(err))
}
render() {
return (
<div>
<form onSubmit={this.getTasks}>
<Form />
</form>
{this.state.data.map((row, i) => (
<div key={i}>
<SampleCard row={row} />
</div>
))}
</div>
)
}
}
export default App;
This, the form component
import React, { Component } from "react";
class Form extends Component {
constructor(props) {
super(props);
this.state = {
tasks: {
task: '',
status: ''
}
}
}
addTask = _ => {
const { tasks } = this.state
fetch(`http://localhost:4000/users/add?task=${tasks.task}&status=${tasks.status}`)
.catch( err => console.log(err))
}
render() {
const { tasks } = this.state
return (
<div className="Form container mt-3">
<div className="input-group mb-3">
<div className="input-group-prepend">
<span className="input-group-text" id="basic-addon1">
Define Task!
</span>
</div>
<input
type="text"
value={tasks.task}
onChange={e => this.setState({ tasks: { ...tasks, task: e.target.value } })}
className="form-control"
placeholder="Task"
aria-label="Task"
aria-describedby="basic-addon1"
/>
</div>
<div className="input-group mb-3">
<div className="input-group-prepend">
<span className="input-group-text" id="basic-addon1">
Define Status!
</span>
</div>
<input
type="text"
value={tasks.status}
onChange={e => this.setState({ tasks: { ...tasks, status: e.target.value } })}
className="form-control"
placeholder="Status"
aria-label="Status"
aria-describedby="basic-addon1"
/>
</div>
<button
type="Submit"
className="btn btn-outline-success d-flex mr-auto"
onClick={this.addTask}
>
Add
</button>
</div>
);
}
}
export default Form;
and this the card component
import React, { Component } from "react";
export default class SampleCard extends Component {
render() {
return (
<div className="container pt-5">
<div className="col-xs-12">
<div className="card">
<div className="card-header">
<h5 className="card-title">{this.props.row.task}</h5>
</div>
<div className="card-body">
<h4 className="card-title">{this.props.row.created_at}</h4>
{this.props.row.status === 1 ? (
<h3 className="card-title">Pending</h3>
) : (
<h3 className="card-title">Completed</h3>
)}
</div>
</div>
</div>
</div>
);
}
}
You're not preventing default submit behavior. Do it like this
getTasks = (e) => {
e.preventDefault();
fetch('http://localhost:4000/users')
.then(response => response.json())
.then(data => this.setState({ data: data.data }))
.catch(err => console.log(err))
}
Whenever using submit with a form, use e.preventDefault() it prevents refreshing.
I used to make this code work out for my search component but after the on submit is called, I receive this error which never happened before, does anyone have any clue???
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
import React, { Component } from "react";
import axios from "axios";
import { Redirect } from "react-router-dom";
import { Consumer } from "../context";
class Search extends Component {
constructor() {
super();
this.state = {
productTitle: "",
apiUrl: "*******************************",
redirect: false
};
}
findProduct = (dispatch, e) => {
e.preventDefault();
axios
.post(
`${this.state.apiUrl}`,
JSON.stringify({ query: this.state.productTitle })
)
.then(res => {
dispatch({
type: "SEARCH_TRACKS",
payload: res.data.output.items
});
this.setState({ items: res.data.output.items, redirect: true });
})
.catch(err => console.log(err));
};
onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
const { redirect } = this.state;
if (redirect) {
return <Redirect to="/searchresult" />;
}
return (
<Consumer>
{value => {
const { dispatch } = value;
return (
<div>
<form onSubmit={this.findProduct.bind(this, dispatch)}>
<div className="form-group" id="form_div">
<input
type="text"
className="form-control form-control-md"
placeholder="...محصولات دسته یا برند مورد نظرتان را انتخاب کنید"
name="productTitle"
value={this.state.productTitle}
onChange={this.onChange}
/>
<button className="btn" type="submit">
<i className="fas fa-search" />
</button>
</div>
</form>
</div>
);
}}
</Consumer>
);
}
}
import React, { Component } from 'react'
import axios from 'axios'
const Context = React.createContext();
export const axiosDashboard = () => {
const URL = (`*****************`);
return axios(URL, {
method: 'POST',
data: JSON.stringify({refresh:"true"}),
})
.then(response => response.data)
.catch(error => {
throw error;
});
};
const reducer = (state, action) => {
switch(action.type){
case 'SEARCH_TRACKS':
return {
...state,
items: action.payload,
heading: 'Search Results'
};
default:
return state;
}
}
export class Provider extends Component {
state = {
dispatch:action => this.setState(state => reducer(state, action))
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
)
}
}
export const Consumer = Context.Consumer
import React, { Component } from 'react'
import { Consumer } from '../context'
import SearchResult from './SearchResult'
import './Search.css'
class Tracks extends Component {
render() {
return (
<Consumer>
{value => {
const { items } = value
if(items === undefined || items.length === 0){
return 'hello'}
else{
return(
<React.Fragment>
<div id='products_search'>
<div className='container'>
<div className="row justify-content-end">
{items.map(item => (
<SearchResult
key={item.id}
id={item.id}
title={item.name}
current_price={item.current_price}
lowest_price={item.lowest_price}
store_name={item.store_name}
thumb={item.thumb_url}/>
))}
</div>
</div>
</div>
</React.Fragment>
)
}
}}
</Consumer>
)
}
}
export default Tracks
import React from 'react'
import {Link} from 'react-router-dom'
import './Search.css'
const SearchResult = (props) => {
const {title,current_price,lowest_price,thumb,id,store_name} = props
return (
<div className="col-md-3" id="searchresult">
<img src={thumb} alt=""/>
<div className="sexy_line"></div>
<p className="muted">{store_name}</p>
<h6>{title}</h6>
<p>{lowest_price}</p>
<Link to={`products/item/${id}`}>
<button type="button" className="btn btn-light rounded-pill">{
new Intl
.NumberFormat({style: 'currency', currency: 'IRR'})
.format(current_price)
}</button>
</Link>
</div>
)
}
export default SearchResult
Maximum update depth exceeded.
This means that you are in a infinit loop of re rendering a component.
The only place where I can see this is possible to happen is in this part
if (redirect) {
return <Redirect to="/searchresult" />;
}
Maybe you are redirecing to the a route that will get the same component that have the redirect.
Please check if you aren't redirecting to the same route as this component and provide your routes and what is inside Consumer.
I have a list of comments on soccer champions, and am trying to display comments of each soccer champion separately. I'm trying to order by id in firebase, but don't know how I can pass id from the champion component to the component where I display all the comments. It's just giving me undefined for some reason. Any help is greatly appreciated!
champ.js
import React, { Component } from "react";
import { ChampsRef, timeRef } from "./reference";
import { getsinglechamp } from "../actions/champs";
import { connect } from "react-redux"; // this is not being used. oh isee so like this?
import { Redirect, Link } from "react-router-dom";
import { Container, Row, Col } from "reactstrap";
import { Upvote } from "react-upvote";
import Form from "./Form";
import { Icon } from "react-icons-kit";
import Specials from "./specials";
import app from "../config/dev";
import { chevronDown } from "react-icons-kit/fa/chevronDown";
import { chevronUp } from "react-icons-kit/fa/chevronUp";
class OneChamp extends Component {
state = {
name: "",
weak: [],
img: "",
authenticated: false,
currentUser: null,
email: ""
};
componentDidMount() {
app.auth().onAuthStateChanged(user => {
if (user) {
this.setState({
currentUser: user,
email: user.email,
authenticated: true
});
} else {
this.setState({
currentUser: null,
authenticated: false
});
}
});
}
componentWillMount() {
const { dispatch, match } = this.props;
dispatch(getsinglechamp(match.params.id));
console.log(this.props);
}
render() {
console.log(this.props.champ);
const { dispatch, loading } = this.props;
const authenticated = this.state.authenticated;
console.log("change", this.props);
console.log("c", this.props.champ.img);
console.log("", this.props.champ.stats);
const champ = this.props.champ.stats;
let content = null;
if (loading) {
content = <p>Loading...</p>;
} else {
content = (
<div id="f">
<div className="ChampionHeader_row_ArTlM">
<div
style={{
paddingRight: "0.75rem",
paddingLeft: "0.75rem",
flexGrow: 1
}}
>
<div style={{ display: "flex", marginBottom: "1.5rem" }}>
<div style={{ flexShrink: 0, marginRight: "1rem" }}>
<div className="IconChampion_component_2qg6y IconChampion_lg_2QLBf">
<img
className="v-lazy-image v-lazy-image-loaded IconChampion_img_3U2qE"
src={this.props.champ.img}
height="80px"
/>
</div>
</div>
</div>
</div>
</div>
<div className="timeline-panel">
<div className="timeline-heading">
{" "}
<h4>{this.props.champ.name}</h4>
</div>
<ul>
{Object.keys(champ).map((item, i) => (
<div className="card">
<li className="travelcompany-input" key={i}>
<div> {champ[item]}</div>
</li>
</div>
))}
</ul>
<br />
<div className="w3-container">
// place i want to pass id <Comments id={this.props.champ.id} />
<h2>Weak To</h2> <br />
<ul className="w3-ul w3-card-4">
<li className="w3-bar">
<img
src={this.props.champ.img2}
className="w3-bar-item w3-circle w3-hide-small"
style={{ width: 145 }}
/>
<div className="w3-bar-item">
<span className="w3-large">{this.props.champ.weak}</span>
<br />
<span id="item"> Mid </span>
<div className="col-sm-5">
<span className="label label-primary">
{this.props.champ.win}
</span>
</div>
</div>
</li>
<li className="w3-bar">
<img
src={this.props.champ.img3}
className="w3-bar-item w3-circle w3-hide-small"
style={{ width: 145 }}
/>
<div className="w3-bar-item">
<Link to={`/Matchup/${this.props.champ.id}`}>
{" "}
<span className="w3-large">{this.props.champ.weak3}</span>
<br />{" "}
</Link>
<span id="item"> Mid </span>
<span className="label label-primary">
{this.props.champ.win}
</span>
</div>
</li>
</ul>
</div>
</div>
<div />
{authenticated ? (
<div className="nav-item">
<Form id={this.props.champ.id} />
</div>
) : (
<div className="nav-item">
<Link to="/login" className="nav-link2">
{" "}
Login to post
</Link>
</div>
)}
</div>
);
}
return <div>{content}</div>;
}
}
const mapStateToProps = state => {
console.log("champs", state.champs);
console.log(state.loading);
return {
champ: state.champs.champ,
loading: state.loading
};
};
export default connect(
mapStateToProps,
null
)(OneChamp);
comments.js
import React, { Component } from "react";
import axios from "axios";
import app from "../config/dev";
import { Link } from "react-router-dom";
import { ChampsRef, CommentsRef, timeRef } from "./reference";
import { connect } from "react-redux";
import { getsinglechamp } from "../actions/champs";
class Comments extends Component {
state = {
comments: [],
champ_id: "",
loading: true,
email: ""
};
componentWillMount() {
const champ_id = this.props.champ.id;
console.log("id", this.props.champ);
CommentsRef.orderByChild("champ_id")
.equalTo(`${champ_id}`)
.on("value", snap => {
const tasks = [];
let comments = [];
snap.forEach(child => {
comments.push({ ...child.val(), key: child.key });
});
this.setState({ comments: comments, Loading: false });
});
}
render() {
const { dispatch, loading } = this.props;
const { comments, ChampsLoading } = this.state;
const orderedchamps = comments;
let commentList;
if (ChampsLoading) {
commentList = <div className="TaskList-empty">Loading...</div>;
} else if (comments.length) {
commentList = (
<ul className="TaskList">
{comments.map(comment => (
<div>{comment.text}</div>
))}
</ul>
);
}
return (
<div>
<h1> Popular Cham</h1>
<p> {commentList} </p>
</div>
);
}
}
const mapStateToProps = state => {
console.log("champs", state.champs);
console.log(state.loading);
return {
champ: state.champs.champ,
loading: state.loading
};
};
export default connect(
mapStateToProps,
null
)(Comments);
My code works perfectly fine when executing CRUD operations. However, I have a small issue when I was trying to edit or delete my project, which is located in ProjectEdit.js file. I could edit and delete the project and store the updated data to the MongoDB, but when I used history.push in editProject and deleteProject actions to redirect my ProjectEdit/ProjectDetail page to a Project page, the project list still shows the project that I have already edited plus an undefined key project in which I don't know how it exists. However, if I refreshed the page, there would be no problems at all. I think the problem might be Redux store that holds state tree of my application, but I am not sure. Therefore, I was wondering if you guys could help me out.
Here's an undefined project key.
My project repo is here: https://github.com/topkoong/PersonalWebsite
My staging website is the following: https://shielded-springs-57013.herokuapp.com/
Thank you for your time and consideration.
ProjectEdit.js
import _ from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm, Field } from 'redux-form';
import { Link } from 'react-router-dom';
import ProjectField from './ProjectField';
import formFields from './formFields';
import * as actions from '../../actions';
import { withRouter } from "react-router-dom";
class ProjectEdit extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
const { _id } = this.props.match.params;
this.props.fetchProject(_id);
}
componentWillReceiveProps({ project }) {
if (project) {
const { title, technology, description, creator } = project;
this.setState({ title, technology, description, creator });
}
}
onHandleSubmit = (event) => {
event.preventDefault();
event.stopPropagation();
const { history} = this.props;
this.props.editProject(this.props.match.params._id, this.state, history);
//this.props.history.push("/project");
}
render() {
return(
<div>
<form onSubmit={this.onHandleSubmit}>
<div className="input-field">
<input
value={this.state.title}
onChange={e => this.setState({ title: e.target.value })}
placeholder="Title"
/>
</div>
<div className="input-field">
<input
value={this.state.technology}
onChange={e => this.setState({ technology: e.target.value })}
placeholder="Technology"
/>
</div>
<div className="input-field">
<input
value={this.state.description}
onChange={e => this.setState({ description: e.target.value })}
placeholder="Description"
/>
</div>
<div className="input-field">
<input
value={this.state.creator}
onChange={e => this.setState({ creator: e.target.value })}
placeholder="Creator"
/>
</div>
<Link to="/project" className="red btn-flat white-text">
Cancel
</Link>
<button type="submit" className="teal btn-flat right white-text">
Submit
<i className="material-icons right">done</i>
</button>
</form>
</div>
);
}
}
// ownProps is the prop obj that is going to ProjectDetail component up top.
const mapStateToProps = ({ projects, auth }, ownProps) => {
return { auth, project: projects[ownProps.match.params._id]};
}
export default connect(mapStateToProps, actions)(withRouter(ProjectEdit));
ProjectDetail.js – using this component to display and delete a project
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchProject, deleteProject } from '../../actions';
import { Link } from 'react-router-dom';
import { withRouter } from "react-router-dom";
class ProjectDetail extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
const { _id } = this.props.match.params;
this.props.fetchProject(_id);
}
onDeleteClick() {
console.log("Delete Project was clicked");
const { history } = this.props;
this.props.deleteProject(this.props.project._id, history);
}
render() {
const { project } = this.props;
if (!project) {
return <div>Loading...</div>;
}
const { title, technology, description, creator, datePosted } = project;
return (
<div>
<div className="row">
<div className="col s12 m9">
<h3>{title}</h3>
<h5>Technologies used: {technology}</h5>
<div className="card story">
<div className="card-content">
<span className="card-title">{new Date(datePosted).toLocaleDateString()}</span>
{description}
</div>
</div>
</div>
<div className="col s12 m3">
<div className="card center-align">
<div className="card-content">
<span className="card-title">{this.props.auth.displayName}</span>
<img className="circle responsive-img" src={this.props.auth.image}/>
</div>
</div>
</div>
</div>
<div className="row col s12">
<div className="col s6 offset-s1">
<Link className="waves-effect waves-light btn-large" to="/project">Back to project</Link>
</div>
<button className="btn-large red" onClick={() => this.onDeleteClick()}>Delete</button>
</div>
</div>
);
}
}
// ownProps is the prop obj that is going to ProjectDetail component up top.
function mapStateToProps({ projects, auth }, ownProps){
return { auth, project: projects[ownProps.match.params._id] };
}
export default connect(mapStateToProps, { fetchProject, deleteProject })(withRouter(ProjectDetail));
Project.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import ProjectList from './project/ProjectList';
import { Link } from 'react-router-dom';
class Project extends Component {
render() {
return(
<div>
<h2>Project</h2>
<ProjectList />
{this.props.auth ? <div className="fixed-action-btn">
<Link to="/project/new" className="btn-floating btn-large red">
<i className="material-icons">add</i>
</Link>
</div> : ''}
</div>
);
}
}
function mapStateToProps({ auth }) {
return { auth };
}
export default connect(mapStateToProps)(Project);
ProjectList.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchProjects } from '../../actions';
import { Link } from 'react-router-dom';
import _ from 'lodash';
class ProjectList extends Component {
componentDidMount() {
this.props.fetchProjects();
}
renderProjects() {
// return this.props.projects.reverse().map(project => {
return _.map(this.props.projects, project => {
return (
<div className="row" key={project._id}>
<div className="col s12 m6">
<div className="card">
<div className="card-image">
{this.props.auth ? <Link to={`/project/${project._id}/edit`} className="btn-floating halfway-fab waves-effect waves-light red">
<i className="material-icons">edit</i></Link> : ''}
</div>
<div className="card-content">
<span className="card-title">{project.title}</span>
<p>
<b>Technologies used: {project.technology} </b>
</p>
<p>
<b>Creator: {project.creator}</b>
</p>
<p>
<b>Project description: </b>{project.description}
</p>
<p className="right">
<b>Posted on: </b>{new Date(project.datePosted).toLocaleDateString()}
</p>
</div>
<div className="card-action">
<Link to={`/project/${project._id}`}>Read more</Link>
</div>
</div>
</div>
</div>
);
})
}
render() {
return (
<div>
{this.renderProjects()}
</div>
);
}
}
function mapStateToProps({ projects, auth }) {
return { projects, auth };
}
export default connect(mapStateToProps, { fetchProjects })(ProjectList);
Actions
export const editProject = (id, values, history) => async dispatch => {
const res = await axios.put(`/api/projects/${id}/edit`, values);
dispatch({ type: FETCH_PROJECTS, payload: res.data });
history.push('/project');
}
export const deleteProject = (id, history) => async dispatch => {
const res = await axios.delete(`/api/projects/${id}`);
dispatch({ type: FETCH_PROJECTS, payload: res.data });
history.push('/project');
}
Reducers
import mapKeys from 'lodash/mapKeys';
import { FETCH_PROJECT, FETCH_PROJECTS } from '../actions/types';
export default function(state = [], action) {
switch (action.type) {
case FETCH_PROJECTS:
return { ...state, ...mapKeys(action.payload, '_id') };
case FETCH_PROJECT:
const project = action.payload;
return { ...state, [project._id]: project };
default:
return state;
}
}
API
const mongoose = require('mongoose');
const requireLogin = require('../middlewares/requireLogin');
const Project = mongoose.model('projects');
module.exports = app => {
// show single project
app.get('/api/projects/:id', async (req, res) => {
const project = await Project.findOne({
_id: req.params.id
});
res.send(project);
});
// edit single project
app.put('/api/projects/:id/edit', requireLogin, async (req, res) => {
try {
const project = await Project.findOne({
_id: req.params.id
});
project.title = req.body.title;
project.technology = req.body.technology;
project.description = req.body.description;
project.creator = req.body.creator;
project.datePosted = Date.now();
project._user = req.user.id;
await project.save();
res.send(project);
} catch (err) {
res.status(422).send(err);
}
});
// fetch projects
app.get('/api/projects', async (req, res) => {
const projects = await Project.find({ _user: req.user.id });
// const projects = await Project.find({
// creator: "Theerut Foongkiatcharoen"
// });
res.send(projects);
});
// create a new project
app.post('/api/projects', requireLogin, async (req, res) => {
const { title, technology, description, creator } = req.body;
const project = new Project({
title,
technology,
description,
creator,
datePosted: Date.now(),
_user: req.user.id
});
try {
await project.save();
const user = await req.user.save();
res.send(user);
} catch (err) {
res.status(422).send(err);
}
});
// delete a single project
app.delete('/api/projects/:id', requireLogin, async (req, res) => {
try {
const project = await Project.remove({
_id: req.params.id
});
res.sendStatus(200);
} catch (err) {
res.status(422).send(err);
}
});
};
In the code:
const res = await axios.delete(`/api/projects/${id}`);
dispatch({ type: FETCH_PROJECTS, payload: res.data }.
Isn't this API endpoint returning only a HTTP status code, which you are then sending to reducer?
This explains your undefined key. Its undefined because you set it to non existing data from response.
I am new to react.In my application I have to perform crud action on inputset(contains one selectbox and textarea) within form.Form loads with one inputset with default values.
Actions
Onchange of inputset text it should update state if not it should
store with default values.
OnAdd it should check previous element has not empty description.
Paricular inputSet should be removed and on clearing description
also it should remove that inputSet
Image:
Code:
Item
import React from 'react';
import { Input, Button, Glyphicon } from 'react-bootstrap';
export default class TextBoxesSet extends React.Component {
constructor(props) {
super(props);
}
_onRemoveName = () => {
console.log('remove');
}
static getPropsFromStores() {
return {language: 'en', description: ''};
}
render() {
return (
<div >
<div className="">
<Input type="select" placeholder="select" wrapperClassName="col-xs-4"
value={this.props.language} onChange={this.handleChange.bind(this, 'language')} >
<option value="en">en</option>
<option value="de">de</option>
</Input>
</div>
<div className="col-md-7">
<Input type="textarea" wrapperClassName="col-xs-12" value={this.props.description}
onChange={this.handleChange.bind(this, 'description')} />
</div>
<div className="col-md-1">
<Button bsSize="small"><Glyphicon glyph="remove" onClick={this._onRemoveName} /></Button>
</div>
</div>
);
}
handleChange(name, e) {
const obj = {};
obj[name] = e.target.value;
this.setState(obj);
if (this.state.language !== undefined && this.state.description !== undefined) {
this.props.handleChange('name', this.state);
}
}
}
TextBoxesSet.propTypes = {
language: React.PropTypes.string,
description: React.PropTypes.string,
handleChange: React.PropTypes.func
};
List
import React from 'react';
import { Button, Glyphicon } from 'react-bootstrap';
// import TextBoxStore from 'stores/textBoxStore';
import TextBoxesSet from './descriptionTextBoxes';
export default class TextBoxesSetList extends React.Component {
constructor(props) {
super(props);
}
_onAddName = (label) => {
if (this.state) {
this.state[label].length++;
React.render(<TextBoxesSet labelName="name" language="de" handleChange={this.handleTextBoxSetChange}/>, document.getElementById('another'));
}
}
_onRemoveName = () => {
console.log('remove');
}
render() {
return (
<div >
<ul >
<TextBoxesSet label={this.props.labelName} handleChange={this.props.handleChange}/>
</ul>
<div className="col-md-1">
<Button bsSize="small"><Glyphicon glyph="plus" onClick={this._onAddName} /></Button>
</div>
</div>
);
}
}
TextBoxesSetList.propTypes = {
newItem: React.PropTypes.string,
list: React.PropTypes.array,
labelName: React.PropTypes.string,
handleChange: React.PropTypes.func
};
Form
import React from 'react';
import { Input, Button, Glyphicon, ButtonToolbar } from 'react-bootstrap';
import AttributeSectionStore from 'stores/attributeSection/AttributeSectionStore';
import TextBoxesSetList from '../TextBoxesList';
import styles from 'scss/_common';
export default class Name extends React.Component {
constructor(props) {
super(props);
this.handleTextBoxSetChange = this.handleTextBoxSetChange.bind(this);
}
_onCreate = () => {
console.log('___________', this.state);
}
static getPropsFromStores() {
return AttributeSectionStore.getState();
}
render() {
return (
<div className="container">
<div className={styles.mainheader}>
<h2 >New Form</h2>
</div>
<div className="col-md-9">
<form className="form-horizontal">
<div className="row">
<div className="col-xs-2">
<label className="">Name</label>
</div>
<div className="col-md-6">
<TextBoxesSetList labelName="name" handleChange={this.handleTextBoxSetChange}/>
</div>
</div>
</form>
</div>
</div>
);
}
handleChange(name, e) {
const change = {};
change[name] = e.target.value;
this.setState(change);
}
handleTextBoxSetChange(label, obj) {
const change = {};
if (this.state[label] === undefined) {
console.log('--------------------');
change[label] = [];
change[label].push(obj);
}
this.setState(change);
}
}
Name.propTypes = {
name: React.PropTypes.arrayOf(React.PropTypes.object)
};
Please help me in solving?