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
Related
how to add an action when the add button is clicked then the item will display the product?
This code does not display the product when clicked
I've been fiddling with it but it's still an error, who knows, someone here can help me and fix the code and explain it
cartAction.js
import {ADD_TO_CART} from './ActionType';
export const addToCart = (items) =>{
return{
type: ADD_TO_CART,
items //I'm confused about what to do in the action payload here
}
}
cartReducer.js
import ImgT from './images/image-product-1-thumbnail.jpg';
import {ADD_TO_CART} from './ActionType';
const initState = {
items: [
{id:1,title:'title',
brand:cc',
images:ImgT,
desc:'helo world',
price:125.00}
],addItems:[],
total:0
}
const cartReducer= (state = initState,action)=>{
if(action.type === ADD_TO_CART){
return {...state, addItems:state.addItems} //I'm confused about what to do in the action payload here
}
return state
}
export default cartReducer;
cart.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Cart extends Component {
render() {
return (
<div>
{this.props.Items.map(item =>{
<div key={item.id}>
{/* <img src={item.images} /> */}
<p>{item.title}</p>
<h4>brand: {item.brand}</h4>
<p>{item.price}</p>
</div>
})
}
</div>
);
}
}
const mapToProps = (state ) =>{
return{
Items:state.addItems}
}
export default connect(mapToProps)(Cart);
Home.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Cart from './Cart';
import {addToCart} from './cartAction';
class Home extends Component{
handleClick = () =>{
this.props.addToCart()
}
render(){
let item = this.props.items.map(item =>{
return(
<div className='card' key={item}>
<img style={{width:'10rem'}} src={item.images}/>
<p className='card-title '>{item.title}</p>
<h2 className='card-title fs-4'>{item.brand}</h2>
<button onClick={() => this.handleClick()} className='btn btn-primary'>Add to cart</button>
<h3 className='fs-5'>{item.price}</h3>
</div>
)
})
return(
<div className="container">
<h3>Home</h3>
{item}
<Cart/>
</div>
)
}
}
const mapToProps = (state) => {
return{
items:state.items,
}
}
const mapDispatchToProps = (dispatch) => {
return{
addToCart: () => dispatch(addToCart())}
}
export default connect(mapToProps,mapDispatchToProps)(Home);
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I wrote a react redux app, everyting working fine, now i am trying implement my initial data should be comes from API endpoint,
this is my postReducers.py file:
var initialState = {
employees: []
};
var postReducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD_POST':
return {
...state,
employees: [...state.employees, action.data],
};
case 'EDIT_POST':
return {
...state,
employees: state.employees.map(emp => emp.id === action.data.id ? action.data : emp)
};
case 'DELETE_POST':
console.log(action.data.id)
return {
...state,
employees: [...state.employees.filter((post)=>post.id !== action.data.id)],
};
default:
return state;
}
};
export default postReducer;
and this is my table.js file
import React, {Fragment} from "react";
import { connect } from "react-redux";
const axios = require('axios');
class Table extends React.Component {
onEdit = (item) => { //Use arrow function to bind `this`
this.props.selectedData(item);
}
onDelete = (id) => {
const data = {
id,
}
this.props.dispatch({ type: 'DELETE_POST', data });
}
render() {
return (
<Fragment>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Age</th>
<th scope="col">Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
{this.props.employees.map((item, index) => (
<tr key={index}>
<td>{item.name}</td>
<td>{item.age}</td>
<td>
<button
type="button"
onClick={() => this.onEdit(item)}>EDIT
</button>
<button
onClick={ () => this.onDelete(item.id) }>DELETE
</button>
</td>
</tr>
))}
</tbody>
</Fragment>
);
}
}
const mapStateToProps = (state) => ({ employees: state.employees });
export default connect(mapStateToProps)(Table);
and this is my App.js file
import React, { Component } from 'react';
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
import Home from '../components/home'
import Contacts from '../components/contact'
import About from '../components/about'
class App extends Component {
render() {
return (
<Router>
<p>A simple webpack for django and react</p>
<div>
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<ul className="navbar-nav mr-auto">
<li><Link to={'/'} className="nav-link"> Home </Link></li>
<li><Link to={'/contact'} className="nav-link">Contact</Link></li>
<li><Link to={'/about'} className="nav-link">About</Link></li>
</ul>
</nav>
<hr />
<Switch>
<Route exact path='/' component={Home} />
<Route path='/contact' component={Contacts} />
<Route path='/about' component={About} />
</Switch>
</div>
</Router>
);
}
}
export default App;
and this is my home.js file
import React from "react"
import Table from "../components/table"
import Form from '../components/form'
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedData: {name: '', age: ''},
isEdit: false
};
}
selectedData = (item) => {
this.setState({selectedData: item,isEdit:true})
}
render() {
return (
<React.Fragment>
<Form selectedData={this.state.selectedData} isEdit={this.state.isEdit}/>
<table>
<Table selectedData={this.selectedData} />
</table>
</React.Fragment>
);
}
}
export default Home;
this is my Form.js file:
import React, { Fragment } from "react"
import { connect } from 'react-redux'
const axios = require('axios');
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
id: this.props.selectedData.id,
name: this.props.selectedData.name,
age: this.props.selectedData.age,
};
this.onHandleChange = this.onHandleChange.bind(this);
this.submit = this.submit.bind(this);
}
submit(event) {
const data = {
name: this.state.name,
age: this.state.age,
email: this.state.email
};
if (this.props.isEdit) {
data.id = this.props.selectedData.id;
console.log('edit', data);
this.props.dispatch({ type: 'EDIT_POST', data })
} else {
// generate id here for new emplyoee
this.props.dispatch({ type: 'ADD_POST', data })
}
}
onHandleChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
componentDidUpdate(prevProps) {
if (prevProps.selectedData.age !== this.props.selectedData.age) { //Check on email, because email is unique
this.setState({ name: this.props.selectedData.name, age: this.props.selectedData.age })
}
}
render() {
return (
<form>
<div className="form-group">
<input onChange={(event) => this.onHandleChange(event)} value={this.state.name} name="name" type="text" />
</div>
<div className="form-group">
<input onChange={(event) => this.onHandleChange(event)} value={this.state.age} name="age" type="number" />
</div>
<button onClick={(event) => this.submit(event)} type="button">
{this.props.isEdit ? 'Update' : 'SAVE'}
</button>
</form>
);
}
}
export default connect(null)(Form);
and this is my index.js file:
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from 'react-router-dom'
import App from "../client/components/App";
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import postReducer from '../client/postReducer'
const store = createStore(postReducer)
// if we don't subcribe, yet our crud operation will work
function onStateChange() {
const state = store.getState();
}
store.subscribe(onStateChange)
ReactDOM.render((
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>
), document.getElementById('app'))
Can anyone please help me to implement my initialState, so that when i visit the page or whe the table is loaded, the table should get data from API end point?
My API End point is: http://127.0.0.1:8000/employees/ it has only two field, name, and age
I am using axios for http request
I searched on stackoverflow and a man showed a possible duplicate post but that didnt solve my problem, coz, it was for react native
You can fetch the data in your home.js where you are using your Table component. You can fetch the data in componentDidMount() and pass data to your reducer using an action like SET_EMPLOYEES_LIST:
home.js:
import React from "react"
import Table from "../components/table"
import Form from '../components/form'
import { setEmployeesList } from './actions'
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedData: {name: '', age: ''},
isEdit: false
};
}
componentDidMount(){
Axios.get(URL).then(res => this.props.setEmployeesList(res.data))
}
selectedData = (item) => {
this.setState({selectedData: item,isEdit:true})
}
render() {
return (
<React.Fragment>
<Form selectedData={this.state.selectedData} isEdit={this.state.isEdit}/>
<table>
<Table selectedData={this.selectedData} />
</table>
</React.Fragment>
);
}
}
const mapDispatchToProps = dispatch => {
return {
setEmployeesList: data => dispatch(setEmployeesList(data)) // Set the action you created in your component props
}
}
export default Home;
action.js:
import {SET_EMPLOYEES_LIST} from './constants'
export const setEmployeesList = payload => ({ type: SET_EMPLOYEES_LIST, payload })
constants.js:
export const SET_EMPLOYEES_LIST = 'SET_EMPLOYEES_LIST'
postReducers.js:
import {SET_EMPLOYEES_LIST} from './constants'
const initialState = {
employees: []
};
const postReducer = (state = initialState, action) => {
switch (action.type) {
case SET_EMPLOYEES_LIST:
return {
...state,
employees: action.payload,
};
case 'ADD_POST':
return {
...state,
employees: [...state.employees, action.data],
};
case 'EDIT_POST':
return {
...state,
employees: state.employees.map(emp => emp.id === action.data.id ? action.data : emp)
};
case 'DELETE_POST':
console.log(action.data.id)
return {
...state,
employees: [...state.employees.filter((post)=>post.id !== action.data.id)],
};
default:
return state;
}
};
export default postReducer;
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.
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 Reactjs and have been trying to create a real world website using React-Redux.
Here in my WebApp, when there is a state change in Home (Parent Component) , child component HomePage does not re-render. I will explain in detail below,
This is my Parent Component,
import React, {Component} from 'react';
import NavBar from './../components/NavBar';
import HomePage from './../components/Home/HomePage';
import {connect} from 'react-redux';
import {loadCity} from './../actions/MainPage';
import Footer from './../components/Footer';
import {instanceOf} from 'prop-types';
import {withCookies, Cookies} from 'react-cookie';
class Home extends Component {
constructor(props) {
super(props);
this.rerender = this
.rerender
.bind(this);
this.state = {
renderFlag: false
}
}
static propTypes = {
cookies: instanceOf(Cookies).isRequired
};
componentDidMount() {
this
.props
.loadCity();
}
rerender() {
this.setState({
renderFlag: !this.state.renderFlag
})
}
render() {
return (
<div>
<NavBar placelist={this.props.result} rerenderfun={() => this.rerender()}/>
<HomePage/>
<Footer/>
</div>
);
}
}
const mapStateToProps = (state) => {
return {result: state.cityReducer}
};
const mapDispatchToProps = (dispatch) => {
return {
loadCity: () => {
dispatch(loadCity());
}
};
};
export default withCookies(connect(mapStateToProps, mapDispatchToProps)(Home));
Here in my NavBar component I have to select city. When I click on city a state change occurs in HOME (Parent Component) which causes child components to re-render. But all the child components except HomePage re-render.
But if I remove connect() from HomePage, then this page gets re-rendered.
Here is my code for HomePage,
import React, {Component} from 'react';
import SearchSection from './SearchSection';
import {connect} from 'react-redux';
import {loadInfoData} from './../../actions/MainPage';
import {instanceOf} from 'prop-types';
import {withCookies, Cookies} from 'react-cookie';
import InfoSection from './../../container/Home/InfoSection ';
class HomePage extends Component {
static propTypes = {
cookies: instanceOf(Cookies).isRequired
};
componentWillMount() {
const {cookies} = this.props;
this.setState({
city: cookies.get('place')
});
}
componentDidMount() {
this
.props
.loadInfoData(this.state.city);
}
componentWillUpdate(nextProps, nextState) {
console.log('Component WILL UPDATE!');
}
render() {
return (
<div><SearchSection/> <InfoSection result={this.props.result}/>
</div>
);
}
}
const mapStateToProps = (state) => {
return {data: state.cityReducer}
};
const mapDispatchToProps = (dispatch) => {
return {
loadInfoData: (selectedCity) => {
dispatch(loadInfoData(selectedCity));
}
};
};
export default withCookies(connect(mapStateToProps, mapDispatchToProps)(HomePage));
Please help me find the issue and resolve it. I want HomePage to get re-rendered on Home state change.
UPDATE
Reducer
let defaultState = {
result: "",
info: "",
recentlyadded: ""
}
const cityReducer = (state = defaultState, action) => {
if (action.type === "GET_CITY") {
return {
...state,
result: action.result
}
} else if (action.type === "GET_INFO_DATA") {
return {
...state,
info: action.result
}
} else if (action.type === "GET_MAIN_RECENTLY_ADDED") {
return {
...state,
recentlyadded: action.result
}
} else {
return {
...state
}
}
}
export default cityReducer;
Action
import axios from 'axios';
export function loadCity() {
return (dispatch) => {
return axios
.get("**RESTAPILINK**")
.then((response) => {
dispatch(getPlace(response.data.result));
})
}
}
export function getPlace(result) {
return {type: "GET_CITY", result}
}
export function loadInfoData(selectedCity) {
var url = "**RESTAPILINK**" + selectedCity;
return (dispatch) => {
return axios
.get(url)
.then((response) => {
dispatch(getInfoData(response.data));
})
}
}
export function getInfoData(result) {
return {type: "GET_INFO_DATA", result}
}
export function loadRecentlyAdded(selectedCity) {
var url = "**RESTAPILINK**" + selectedCity;
return (dispatch) => {
return axios
.get(url)
.then((response) => {
dispatch(getRecentlyAdded(response.data));
})
}
}
export function getRecentlyAdded(result) {
return {type: "GET_MAIN_RECENTLY_ADDED", result}
}
My NavBar component
import React, {Component} from 'react';
import {
Collapse,
Navbar,
NavbarToggler,
NavbarBrand,
Nav,
NavItem
} from 'reactstrap';
import {NavLink} from 'react-router-dom';
import cityicon from '../assets/images/city/Bangalore.png';
import {instanceOf} from 'prop-types';
import {withCookies, Cookies} from 'react-cookie';
class NavBar extends Component {
constructor(props) {
super(props);
this.toggle = this
.toggle
.bind(this);
this.toggleHidden = this
.toggleHidden
.bind(this);
this.closeOverlay = this
.closeOverlay
.bind(this);
this.escFunction = this
.escFunction
.bind(this);
this.handleClick = this
.handleClick
.bind(this);
this.state = {
isOpen: false,
isHidden: true,
isLocation: false,
city: "Select your city",
classname: "city-section"
};
}
static propTypes = {
cookies: instanceOf(Cookies).isRequired
};
componentWillMount() {
const {cookies} = this.props;
let newstate = (cookies.get('place') != null)
? true
: false;
if (newstate) {
this.setState({
city: cookies.get('place')
});
}
this.setState({isLocation: newstate, isHidden: newstate});
}
toggle() {
this.setState({
isOpen: !this.state.isOpen
});
}
toggleHidden() {
this.setState({
isHidden: !this.state.isHidden
});
}
closeOverlay() {
this.setState({
isHidden: !this.state.isHidden
});
}
escFunction(event) {
if (event.keyCode === 27) {
if (this.state.isHidden === false) {
this.closeOverlay();
}
}
}
handleClick(selectedCity) {
const {cookies} = this.props;
cookies.set("place", selectedCity);
this
.props
.rerenderfun();
if (this.state.isLocation) {
this.setState({
isHidden: !this.state.isHidden,
city: cookies.get('place')
})
} else {
this.setState({
isLocation: !this.state.isLocation,
isHidden: !this.state.isHidden,
city: cookies.get('place')
})
}
}
render() {
var overlayClass = 'city-section';
if (!this.state.isLocation) {
overlayClass += " city-section visible"
} else if (this.state.isHidden) {
overlayClass += " city-section hidden";
} else {
overlayClass += " city-section visible"
}
return (
<div>
<Navbar className="navbar navbar-expand-md navbar-dark" light expand="md">
<NavbarBrand href="/">
<i className="fa fa-graduation-cap mr-2" aria-hidden="true"></i>Company
</NavbarBrand>
<NavbarToggler onClick={this.toggle}/>
<Collapse isOpen={this.state.isOpen} navbar>
<Nav className="ml-auto" navbar>
<NavItem>
<NavLink className="nav-link" to="/">Favourites</NavLink>
</NavItem>
<NavItem>
<NavLink to="/" className="nav-link">Login</NavLink>
</NavItem>
<NavItem>
<a className="nav-link" onClick={() => this.toggleHidden()}>
<i className="fa fa-map-marker mr-2" aria-hidden="true"></i>{this.state.city}</a>
</NavItem>
</Nav>
</Collapse>
</Navbar>
<div className={overlayClass} onClick={this.closeOverlay}>
<div
className="city-content py-5"
onClick={(e) => {
e.stopPropagation();
}}>
<div className="container">
<div className="row text-center">
<div className="col-12">
<h4 className="text-secondary">Select your City</h4>
</div>
</div>
{Object
.entries(this.props.placelist.result)
.map(([k, value]) => {
return (
<div className="row text-center mt-5" key={k}>
<div className="col-md-2 offset-md-5">
<div
className="card border-0 location-card"
onClick={() => {
this.handleClick(value.city)
}}>
<div className="card-body">
<img className="location-img" src={cityicon} alt="bangalore"/>
<p className="font-weight-bold mt-3 mb-0">{value.city}</p>
</div>
</div>
</div>
</div>
)
})}
<div className="row text-center pt-5">
<div className="col-12">
<h6 className="text-secondary mt-3 font-italic">Currently we are only in Bangalore</h6>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default withCookies(NavBar);
you need to pass loadInfoData bind to dispatch to navbar component. You can write mapdispatchtoProps for navbar also .
handleClick(selectedCity) {
const {cookies, loadInfoData} = this.props;
loadInfoData(selectedCity)
cookies.set("place", selectedCity);
this
.props
.rerenderfun();
if (this.state.isLocation) {
this.setState({
isHidden: !this.state.isHidden,
city: cookies.get('place')
})
} else {
this.setState({
isLocation: !this.state.isLocation,
isHidden: !this.state.isHidden,
city: cookies.get('place')
})
}
}
// add below code for navbar component
const mapDispatchToProps = (dispatch) => {
return {
loadInfoData: (selectedCity) => {
dispatch(loadInfoData(selectedCity));
}
};
};
export default connect(null, mapDispatchToProps)(NavBar)
First of all it's hard to provide a conclusive answer with just the codes you shared, I mean how you wrote the reducers and all.
Nevertheless, I can suggest some best practices regarding react and redux architecture. Like here in your case does both the parent and child component needs to connect to redux store? as the parent component(Home) is connected and it re-renders so the child component(HomePage) can just receive values with props.
For further studies I suggest going through the following.
https://github.com/reactjs/redux/issues/585
https://redux.js.org/docs/Troubleshooting.html
personal suggestions or descriptions:
Thing is there are a lot of best practices and standards and none are absolute or one to rule them all type. It solely depends on your needs and application architecture. But what you need is to grasp some concepts like smart and dumb components or container and presentational components, lifting the state up(react docs explains it best). Try to get you head around these concepts. React being a very well engineered library problems like these wont even appear if you architect your application cleverly.
Knowing about the whole application ahead and being able to divide that into many components with as small footprints as possible is the key here I think.
For start you can first design whole state tree of the application (object structure) if not possible then up to some extent and design the component hierarchy by looking at that state tree, you will find that everything is falling into places.