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

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;

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

React Router/Context API Search Component

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.

Blink previous result before render new result

I am new in react and redux. I am fetching data from one page page and I would like to show details of the record. When I click from the list to see details for the first time, it is fine. But when I would like to see the next result details and for a millisecond there is a blink of previous result and then re rendered by the new one. Could you help me with this issue, please? I do not want to see the previous artist detail.
Show details
import React from 'react';
import { connect } from 'react-redux';
import { fetchArtistDetails } from '../../actions';
class ResultDetails extends React.Component{
componentDidMount(){
this.props.fetchArtistDetails(this.props.match.params.id);
}
renderList(){
return this.props.artist.map(artist => {
return(
<div className="ui segment">
<br/>
<div className="ui two column centered grid">
<div className="ui centered massive label">{artist.name}</div>
</div>
<br/>
<br/>
{ (artist.images) ?
artist.images.map(image =>{
return image.type ==='primary' ? <img className="ui centered medium image" src={image.uri}/> : null
}) : null
}
<div className="ui small images">
{ (artist.images) ?
artist.images.map(image =>{
return image.type ==='secondary' ? <img src={image.uri}/> : null
}) : null
}
</div>
<p>{artist.profile}</p>
</div>
);
});
}
render(){
if (!this.props.artist) {
console.log("ahoj");
return <div>Loading...</div>;
}
return(
<div>
<div>{this.renderList()}</div>
</div>
);
}
}
const mapStateToProps = state => {
return { artist: state.artistDetails }
}
export default connect( mapStateToProps , { fetchArtistDetails }) (ResultDetails);
Actions
import discogs from '../apis/discogs';
import history from '../history';
import { FETCH_POSTS, SEARCH_ARTIST, FETCH_ARTIST_DETAILS, CLEAN_ARTIST_DETAILS } from './types';
export const fetchPosts = text => async dispatch => {
const response = await discogs.get(`/database/search?q=${text}&type=artist`);
dispatch({ type: FETCH_POSTS, payload: response.data.results });
history.push(`/results/${text}`);
};
export const searchArtist = text => dispatch => {
dispatch({
type: SEARCH_ARTIST,
payload: text
});
};
export const fetchArtistDetails = id => async dispatch => {
const response = await discogs.get(`/artists/${id}`);
dispatch({ type: FETCH_ARTIST_DETAILS, payload: response.data });
history.push(`/details/${id}`);
};
Reducer
import {
FETCH_ARTIST_DETAILS,
} from '../actions/types';
export default (state = [], action) => {
switch (action.type) {
case FETCH_ARTIST_DETAILS:
return [action.payload]
default:
return state;
}
};
App
import React, { Fragment } from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import MainPage from '../pages/MainPage';
import SearchResult from '../components/searchResult/SearchResult';
import ResultDetails from '../components/resultDetails/ResultDetails';
import Header from './header/Header';
import history from '../history';
const App = () => {
return (
<div className="ui container">
<Router history={history}>
<div>
<Switch>
<Route exact path='/' component={MainPage} />
<Fragment>
<Header />
<Route exact path="/results/:id" component={SearchResult} />
<Route exact path="/details/:id" component={ResultDetails} />
</Fragment>
</Switch>
</div>
</Router>
</div>
);
};
export default App;
I found and issue (I do not know, if it is proper solution but it works for me)
New action
export const cleanArtistDetails = () => async dispatch => {
dispatch({ type: CLEAN_ARTIST_DETAILS, payload: null });
};
Update reducer
import {
FETCH_ARTIST_DETAILS, CLEAN_ARTIST_DETAILS,
} from '../actions/types';
export default (state = [], action) => {
switch (action.type) {
case FETCH_ARTIST_DETAILS:
return [action.payload]
case CLEAN_ARTIST_DETAILS:
return null
default:
return state;
}
};
And update component
componentWillUnmount(){
this.props.cleanArtistDetails();
}

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.

React & React-Router & Redux rendering of components

I have a simple app. It has two components:
Items - it just renders items from the store
AddItem - it just add an item to the store
The question is: why when I submit the form of AddItem component this component is mounted again? If I don't invoke addItem function there is no rerender of AddItem. Any thoughts?
Live example on WebpackBin
Here is the app
import React, { Component } from 'react';
import { BrowserRouter, Match, Link, Redirect, browserHistory } from 'react-router';
import { Provider, connect } from 'react-redux';
import { createStore, bindActionCreators } from 'redux';
import { render } from 'react-dom';
// ACTIONS
const add = (item) => {
return {
type: 'ADD',
payload: item
}
};
// REDUCER
const initialState = {
items: [
'hello',
'world'
]
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD': {
return { items: [...state.items, action.payload] };
}
default : {
return state;
}
}
}
// STORE
const store = createStore(reducer);
// ADD ITEM
class AddItem extends Component {
constructor(props) {
super(props);
this.state = {
foo: 'bar'
};
}
componentDidMount() {
console.log('mount AddItem');
}
onSubmit(event) {
event.preventDefault();
this.props.addItem(this.itemTitle.value);
this.form.reset();
this.setState({
foo: 'asd'
});
}
render() {
return (
<div>
<form onSubmit={::this.onSubmit} ref={node => this.form = node}>
<div>
<label htmlFor="item-title">Item</label>
</div>
<div>
<input id="item-title" type="text" defaultValue="baz" ref={node => this.itemTitle = node}/>
</div>
<input type="submit" value="Add" />
<div>
{this.state.foo}
</div>
</form>
<button>Click</button>
</div>
);
}
}
// ITEMS
class Items extends Component {
render() {
return (
<ul>
{
this.props.items.map(item => {
return <li key={item}>{item}</li>;
})
}
</ul>
);
}
}
// LAYOUT
class Layout extends Component {
render() {
const MatchWithProps = ({component:Component, ...rest}) => {
<Match {...rest} render={() => (
<Component {...this.props} />
)}/>
};
return (
<BrowserRouter>
<div>
Reload
<br />
<Link activeOnlyWhenExact activeClassName="active" to="/">Items</Link>
<Link activeClassName="active" to="/add">Add Item</Link>
<hr />
<Match exactly pattern="/" render={() => <Items {...this.props} /> } />
<Match exactly pattern="/add" component={() => <AddItem addItem={this.props.itemActions.add} />} />
</div>
</BrowserRouter>
);
}
}
const mapStateToProps = (state) => {
return {
items: state.items
};
}
const itemActions = { add };
const mapDispatchToProps = (dispatch) => {
return {
itemActions: bindActionCreators(itemActions, dispatch)
};
}
const ConnectedLayout = connect(mapStateToProps, mapDispatchToProps)(Layout);
// PROVIDER
const provider = (
<Provider store={store}>
<ConnectedLayout />
</Provider>
);
render(provider, document.querySelector('#react-container'));

Resources