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.
Related
I have one component with a form that creates posts and then I have another component that displays these posts in a feed. How do I get the feed to re-render when a new post is created? What's the best way to do that?
Re-render the parent component without changing props?
Update props?
Some other way?
Create.js
import React, { useState } from "react";
export default function Create () {
const [form, setForm] = useState({
post: "",
});
// These methods will update the state properties.
function updateForm(value) {
return setForm((prev) => {
return { ...prev, ...value };
});
}
async function onSubmit(e) {
e.preventDefault();
// When a post request is sent to the create url, we'll add a new post to the database.
const newPost = { ...form };
await fetch("http://localhost:3000/post/add", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newPost),
})
.catch(error => {
window.alert(error);
return;
});
setForm({ post: "" });
}
return (
<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label htmlFor="post">Post</label>
<input
type="text"
className="form-control"
id="post"
value={form.post}
onChange={(e) => updateForm( { post: e.target.value })}
/>
</div>
<div className="form-group">
<input
type="submit"
value="Create post"
className="btn btn-primary"
/>
</div>
</form>
</div>
);
}
Feed.js
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
const Post = (props) => (
<div>
{props.post.post}
<Link className="btn btn-link" to={`/edit/${props.post._id}`}>Edit</Link>
<button className="btn btn-link" onClick={() => { props.deletePost(props.post._id);
}}
>
Delete
</button>
</div>
);
export default function PostList() {
const [posts, setPosts] = useState([]);
// This method fetches the posts from the database.
useEffect(() => {
async function getPosts() {
const response = await fetch(`http://localhost:3000/post/`);
if (!response.ok) {
const message = `An error occured: ${response.statusText}`;
window.alert(message);
return;
}
const posts = await response.json();
setPosts(posts);
}
getPosts();
return;
}, [posts.length]);
// This method will delete a post
async function deletePost(id) {
await fetch(`http://localhost:3000/${id}`, {
method: "DELETE"
});
const newPosts = posts.filter((el) => el._id !== id);
setPosts(newPosts);
}
// This method will map out the posts on the table
function postList() {
return posts.map((post) => {
return (
<Post
post={post}
deletePost={() => deletePost(post._id)}
key={post._id}
/>
);
});
}
// This following section will display the the posts.
return (
<div>
<h3>Post List</h3>
<div>{postList()}</div>
</div>
);
}
Home.js
import React from "react";
import Feed from "./feed";
import Create from "./create";
const Home = () => {
return (
<div className="app">
<div>
<Create />
<Feed />
</div>
</div>
);
};
export default Home;
There are numerous ways to do so. The one you should start with is the most straightforward: pass your current posts and setPosts into both component from a parent component:
const Parent = () => {
const [posts, setPosts] = useState([]);
return <>
<Create posts={posts} setPosts={setPosts} />
<PostList posts={posts} setPosts={setPosts} />
</>
}
There are better ways to do so, but they are more involved. I suggest you to start with this one to understand how React handles data better.
Note: please pull the repo to get access to the full App, there are too many files to simply share the code here. The repo: https://github.com/liviu-ganea/movie-database.git
So I've asked a question previously about my first React + Redux app and I've improved it. I got 2 problems now. The delete function for each movie (i.e. each entry in the redux state) is working just fine. So I now have problems with Login. Every time I press the button, nothing happens, and I suspect it's because the Login page component receives no props from the redux state.
My reasoning:
handleCLick = () => {
let userName = this.props.user.nickname;
let pw = this.props.user.password;
const nicknameBox = document.getElementById('nickname').textContent;
const passwordBox = document.getElementById('password').textContent.trim;
/*if (nicknameBox === userName && passwordBox === pw) {*/
this.props.loginUser((this.props.user.isLoggedIn = true));
this.props.history.push('/');
//}
};
When commented as it is now, it should go to the home page whenever I click the login button and if the password matches doesn't matter. Only there is no reaction to me pressing the button. Is the problem as I suspect?
And another problem: see in Movie.js I've tried to get the path (it's set in state: movies: cover) to the movie poster (located in ./src) so that when (or if) I make use of an API the path should set itself dynamically (i.e. I won't have to go into every component and add the path manually). Same thing on the Home page (./src/Components/Routes/Home.js)...no posters for any movie.
After Comments:
Component:
import React, { Component } from 'react';
import './routes.css';
import { loginAction } from '../../actions/userActions';
import { connect } from 'react-redux';
class Login extends Component {
handleCLick = () => {
let userName = this.props.user.nickname;
let pw = this.props.user.password;
const nicknameBox = document.getElementById('nickname').textContent;
const passwordBox = document.getElementById('password').textContent.trim;
/*if (nicknameBox === userName && passwordBox === pw) {*/
this.props.loginUser((this.props.user.isLoggedIn = true));
this.props.history.push('/');
//}
};
render() {
console.log(this.props);
return (
<div className="container page-container login-page">
<h3 className="page-title">Log In User</h3>
<div className="login-box">
<div>
<label className="login-labels" htmlFor="nickname">
Login{' '}
</label>
<input type="text" className="nickname-box" name="nickname" id="nickname" />
</div>
<div>
<label className="login-labels" htmlFor="password">
Password{' '}
</label>
<input type="password" className="password-box" name="password" id="pasword" />
</div>
<button className="login-button" onClick={this.handleClick}>
Login
</button>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
user: state.user,
};
};
const mapDispatchToProps = (dispatch) => {
return {
loginUser: (isLoggedIn) => {
dispatch(loginAction(isLoggedIn));
},
};
};
export default connect(mapDispatchToProps, mapStateToProps)(Login);
Reducer:
import { createStore } from 'redux';
const initialState = {
movies: [
{
id: '1',
title: 'John Wick Chapter 2',
year: '2017',
main_actor: 'Keanu Reeves',
summary: `The hitman that's been retired then back then retired strikes again, this time against the Mafia.`,
cover: '../john_wick_2.jpg',
},
{
id: '2',
title: 'Star Wars Revenge of the Sith',
year: '2005',
main_actor: 'Ewan McGregor',
summary: `Anakin betrays Space Jesus so General Kenobi is forced to Mustafar Anakin.`,
cover: '../sw_rots.png',
},
{
id: '3',
title: 'Star Wars The Clone Wars',
year: '2008 - 2020',
main_actor: 'Ewan McGregor, Hayden Christensen',
summary: `Yoda has finally overdosed on Ketamine, Mace Window hasn't been defenestrated yet and The Negotiator has proven himself incapable of falling to the Dark Side`,
cover: '../sw_tcw.jpg',
},
],
user: {
isLoggedIn: 'false',
nickname: 'obi_wan',
password: '12345',
},
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'DELETE_MOVIE':
let newMovieList = state.movies.filter((movie) => {
return action.id !== movie.id;
});
return {
...state,
movies: newMovieList,
};
case 'LOG_IN':
let newUserState = action.isLoggedIn;
return {
...state,
user: { ...state.user, isLoggedIn: action.payload.isLoggedIn },
};
default:
return state;
}
return state;
};
export default rootReducer;
userActions:
export const loginAction = (isLoggedIn) => {
return {
type: 'LOG_IN',
isLoggedIn: 'true',
};
};
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import rootReducer from './reducers/rootReducer';
import App from './App';
const store = createStore(rootReducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
console.log(this.props) for the Login component:
[![enter image description here][1]][1]
Movie.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { deleteMovieAction } from '../actions/movieActions';
import './Routes/routes.css';
class Movie extends Component {
handleClick = () => {
this.props.deleteMovie(this.props.movie.id);
this.props.history.push('/');
};
render() {
console.log(this.props);
const isUser = this.props.user.isLoggedIn ? (
<button className="delete-movie-button" onClick={this.handleClick}>
Delete
</button>
) : null;
const theMovie = this.props.movie ? (
<div className="movie-container">
<img src={this.props.movie.cover} alt={this.props.movie.title} className="movie-cover" />
<div className="movie-container-content">
<h2 className="movie-title">{this.props.movie.title}</h2>
<p className="movie-description">{this.props.movie.summary}</p>
<div className="movie-data">
<p className="movie-year">{this.props.movie.year}</p>
<p className="movie-actor">{this.props.movie.main_actor}</p>
</div>
</div>
{isUser}
</div>
) : (
<div className="center">Getting data about the movie. Please wait.</div>
);
return <div className="container page-container">{theMovie}</div>;
}
}
const mapStateToProps = (state, ownProps) => {
let id = ownProps.match.params.movie_id;
return {
user: state.user,
movie: state.movies.find((movie) => movie.id === id),
};
};
const mapDispatchToProps = (dispatch) => {
return {
deleteMovie: (id) => {
dispatch(deleteMovieAction(id));
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Movie);
Movie.js - every movie component
import React, {Component} from 'react'
import { connect } from 'react-redux';
import { deleteMovieAction } from '../actions/movieActions'
import './Routes/routes.css'
class Movie extends Component {
handleClick = () => {
this.props.deleteMovie(this.props.movie.id);
this.props.history.push('/');
}
render () {
const coverino = ''
console.log(this.props);
const isUser = this.props.isLoggedIn ? ( <button className='delete-movie-button' onClick={this.handleClick}>Delete</button> ) : (null)
const theMovie = this.props.movie ? (
<div className='movie-container'>
<img src={this.props.movie.cover} alt={this.props.movie.title} className='movie-cover'/>
<div className='movie-container-content'>
<h2 className='movie-title'>{this.props.movie.title}</h2>
<p className='movie-description'>{this.props.movie.summary}</p>
<div className='movie-data'>
<p className='movie-year'>{this.props.movie.year}</p>
<p className='movie-actor'>{this.props.movie.main_actor}</p>
</div>
</div>
{isUser}
</div>
) : ( <div className="center">Getting data about the movie. Please wait.</div> );
return (
<div className='container page-container'>
{theMovie}
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
let id = ownProps.match.params.movie_id;
return {
isLoggedIn: state.user.isLoggedIn,
movie: state.movies.find(movie => movie.id === id),
}
}
const mapDispatchToProps = (dispatch) => {
return {
deleteMovie: (id) => { dispatch(deleteMovieAction(id))}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Movie)
The page display the entire movie list
import React, {Component} from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import './routes.css'
class Home extends Component{
render() {
console.log(this.props);
const { movies } = this.props;
const movieList = movies.length ? ( movies.map(movie => {
return (
<Link to={'/' + movie.id} key={movie.id}>
<div className='movie-container'>
<img src={require(`${movie.cover}`)} alt={movie.title} className='movie-cover'/>
<div className='movie-container-content'>
<h2 className='movie-title'>{movie.title}</h2>
<p className='movie-description'>{movie.summary}</p>
<div className='movie-data'>
<p className='movie-year'>{movie.year}</p>
<p className='movie-actor'>{movie.main_actor}</p>
</div>
</div>
</div>
</Link>
)
}
, )) : (<div className='waiting-for-movies'>Loading. Please wait</div>)
return (
<div className='container page-container'>
{movieList}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
movies: state.movies
}
}
export default connect(mapStateToProps)(Home)
neither the require method, nor the simple {this.props...} works
[1]: https://i.stack.imgur.com/51FTy.png
So, as the title suggests, Cards component is receiving props from UserPosts, as well as it's connected to the store to dispatch an action. But it looks like this is not working at all. Connecting a component is not working for me. Maybe I am missing something? Can someone show me the correct way to do it. I'm trying to delete a post on clicking on the delete button.
Here is the code.
UserPosts
import React, { Component } from "react"
import { getUserPosts, getCurrentUser } from "../actions/userActions"
import { connect } from "react-redux"
import Cards from "./Cards"
class UserFeed extends Component {
componentDidMount() {
const authToken = localStorage.getItem("authToken")
if (authToken) {
this.props.dispatch(getCurrentUser(authToken))
if (this.props && this.props.userId) {
this.props.dispatch(getUserPosts(this.props.userId))
} else {
return null
}
}
}
render() {
const { isFetchingUserPosts, userPosts } = this.props
return isFetchingUserPosts ? (
<p>Fetching....</p>
) : (
<div>
{userPosts &&
userPosts.map(post => {
return <Cards key={post._id} post={post} />
})}
</div>
)
}
}
const mapStateToPros = state => {
return {
isFetchingUserPosts: state.userPosts.isFetchingUserPosts,
userPosts: state.userPosts.userPosts,
userId: state.auth.user._id
}
}
export default connect(mapStateToPros)(UserFeed)
Cards
import React, { Component } from "react"
import { connect } from "react-redux"
import { deletePost } from "../actions/userActions"
class Cards extends Component {
handleDelete = postId => {
this.props.dispatch(deletePost(postId))
}
render() {
const { _id, title, description } = this.props.post
return (
<div className="card">
<div className="card-content">
<div className="media">
<div className="media-left">
<figure className="image is-48x48">
<img
src="https://bulma.io/images/placeholders/96x96.png"
alt="Placeholder image"
/>
</figure>
</div>
<div className="media-content" style={{ border: "1px grey" }}>
<p className="title is-5">{title}</p>
<p className="content">{description}</p>
<button className="button is-success">Edit</button>
<button
onClick={this.handleDelete(_id)}
className="button is-success"
>
Delete
</button>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = () => {
return {
nothing: "nothing"
}
}
export default connect(mapStateToProps)(Cards)
deletePost
export const deletePost = (id) => {
return async dispatch => {
dispatch({ type: "DELETING_POST_START" })
try {
const deletedPost = await axios.delete(`http://localhost:3000/api/v1/posts/${id}/delete`)
dispatch({
type: "DELETING_POST_SUCCESS",
data: deletedPost
})
} catch(error) {
dispatch({
type: "DELETING_POST_FAILURE",
data: { error: "Something went wrong" }
})
}
}
}
Should be something like this:
const mapDispatchToProps(dispatch) {
return bindActionCreators({ deletePost }, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Cards)
And then call it as a prop:
onClick={this.props.deletePost(_id)}
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!
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.