React Router/Context API Search Component - reactjs

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.

Related

React component not handling clicks correctly

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

Component is receiving props from another component as well is connected to the store to call the action

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

React Redux set Initial State with getting data from API [closed]

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;

Where do I add a click event in reactjs

Maybe this doesen't even look like a code, but is there any way I can change other components value/state on click?
import React from 'react';
import './pokemonList.css';
import {Component} from 'react';
import Pokemon from './Pokemon';
class PokemonList extends Component {
constructor(props){
super(props);
this.state = {
pokemons : [],
pokemon : {}
};
}
componentWillMount(){
fetch('https://pokeapi.co/api/v2/pokemon/').then(res=>res.json())
.then(response=>{
this.setState({
pokemons : response.results,
});
});
}
handleClick(id) {
fetch(`https://pokeapi.co/api/v2/pokemon/${id}/`)
.then(res => res.json())
.then(data => {
const pokemon = new Pokemon(data);
this.setState({ pokemon: pokemon });
})
.catch(err => console.log(err));
console.log("click happened");
}
render(){
const {pokemons} = this.state;
return (
<div className='pokemonList'> {pokemons.map(pokemon =>(
<button onClick={this.handleClick.bind(this)} className='pokemon-
btn' key={pokemon.name}>
{pokemon.name}
</button>
))}
</div>
)
}}
export default PokemonList;
At this point I'm not even sure where does handleClick() has to be, so I put it in my App component aswell. The output is ok, but clicking these buttons doesen't seem to do anything. They are supposed to show detailed pokemon information in component.
import React, {Component} from 'react';
import './pokemon-info.css';
const PokemonInfo = ({ pokemon }) => {
const { name,
height,
weight,
sprite,
statsSpeed,
statsSpecialDefense,
statsSpecialAttack,
statsDefense,
statsAttack,
statsHp
} = pokemon;
return (
<section className="pokemonInfo">
<img src={sprite} className='sprite-image' alt="pokemon_sprite"/>
<div className='data-wrapper'>
<h3 className="data-char">{pokemon.name}</h3><br />
<p className = 'data-char'>Height: {height}</p>
<p className = 'data-char'>Weight: {weight}</p><br />
<p className = 'data-char'>Stats: </p><br />
<p className = 'data-char'>Speed: {statsSpeed}</p>
<p className = 'data-char'>Special defense: {statsSpecialDefense}</p>
<p className = 'data-char'>Special attack: {statsSpecialAttack}</p>
<p className = 'data-char'>Defense: {statsDefense}</p>
<p className = 'data-char'>Attack: {statsAttack}</p>
<p className = 'data-char'>Hp: {statsHp}</p>
</div>
</section>
)
}
export default PokemonInfo;
Here is my App component
import React, { Component } from 'react';
import './App.css';
import PokemonList from './PokemonList';
import Pokemon from './Pokemon';
import PokemonInfo from './PokemonInfo';
class App extends Component {
constructor() {
super();
this.state = {
pokemon: {}
};
this.handleOnClick = this.handleOnClick.bind(this);
}
handleOnClick(id) {
fetch(`http://pokeapi.co/api/v2/pokemon/${id}/`)
.then(res => res.json())
.then(data => {
const pokemon = new Pokemon(data);
this.setState({ pokemon });
})
.catch(err => console.log(err));
}
render() {
return (
<div className="App">
<PokemonList />
<PokemonInfo pokemon={this.state.pokemon}/>
</div>
);
}
}
export default App;
It is obvious I did go wrong somewhere, but where?
Update:
Pokemon
class Pokemon {
constructor(data) {
this.id = data.id;
this.name = data.name;
this.height = data.height;
this.weight = data.weight;
this.sprite = data.sprites.front_default;
this.statsSpeed = data.stats[0].stats.base_stat;
this.statsSpecialDefense = data.stats[1].stats.base_stat;
this.statsSpecialAttack = data.stats[2].stats.base_stat;
this.statsDefense = data.stats[3].stats.base_stat;
this.statsAttack = data.stats[4].stats.base_stat;
this.statsHp = data.stats[5].stats.base_stat;
}
}
export default Pokemon;
Your App component should keep the state and pass updater functions as props to children components:
PokemonList
import React from "react";
import "./pokemonList.css";
import { Component } from "react";
import Pokemon from "./Pokemon";
class PokemonList extends Component {
render() {
const { pokemons } = this.props;
return (
<div className="pokemonList">
{pokemons.map(pokemon => (
<button
onClick={() => this.props.handleClick(pokemon.id)} // id or whatever prop that is required for request
className="pokemon-btn"
key={pokemon.name}
>
{pokemon.name}
</button>
))}
</div>
);
}
}
PokemonInfo - no change here.
APP
import React, { Component } from "react";
import "./App.css";
import PokemonList from "./PokemonList";
import Pokemon from "./Pokemon";
import PokemonInfo from "./PokemonInfo";
class App extends Component {
constructor() {
super();
this.state = {
pokemon: {},
pokemons: [],
};
this.handleOnClick = this.handleOnClick.bind(this);
}
componentDidMount() {
fetch("https://pokeapi.co/api/v2/pokemon/")
.then(res => res.json())
.then(response => {
this.setState({
pokemons: response.results
});
});
}
handleOnClick(id) {
fetch(`http://pokeapi.co/api/v2/pokemon/${id}/`)
.then(res => res.json())
.then(data => {
const pokemon = new Pokemon(data);
this.setState({ pokemon });
})
.catch(err => console.log(err));
}
render() {
return (
<div className="App">
<PokemonList pokemons={this.state.pokemons} handleClick={this.handleOnClick} />
<PokemonInfo pokemon={this.state.pokemon} />
</div>
);
}
}
More on lifting the state up.

React-Redux : Child component not re-rendering on parent state change when using connect() in child

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.

Resources