React & React-Router & Redux rendering of components - reactjs

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'));

Related

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;

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

Add a comment without refreshing the page

I have a comment form and I can add comments, but I have to refresh the page for them to display
the comment form looks like this
class CommentForm extends PureComponent {
constructor(props) {
super(props);
this.state = {
state: this.props.state
};
this.handleChange = this.handleChange.bind(this);
}
handleSubmit = e => {
e.preventDefault();
this.props.onSubmit(this.state);
};
handleChange = event => {
const { name, value } = event.target;
this.setState({
[name]: value
});
};
render() {
return (
<div>
<Paper className="styles" elevation={4}>
<form onSubmit={this.handleSubmit}>
<div>
<TextField
label="Comment"
name="comment"
value={this.state.comment || ""}
onChange={this.handleChange}
/>
</div>
<br />
<Button type="submit">Save</Button>
</form>
</Paper>
</div>
);
}
}
const mapStateToProps = state => {
return {
state: state
};
};
export default connect(
mapStateToProps,
{ getTicket }
)(CommentForm);
And this component is for displaying the comments that are saved in the database
displayComments
class DisplayComments extends PureComponent {
componentWillMount() {
this.props.getAllComments();
}
getComment(commentId) {
this.props.getComment(commentId);
}
render() {
const { comments } = this.props;
const commentsOnTicket = this.props.data.match.params.id
const filterComments = comments.filter(comment => comment.tickets.id == commentsOnTicket);
const commentsList = filterComments.sort((a, b) => {
return a.id - b.id;
});
return (
<div>
<Paper className="styles" elevation={4}>
<h1>comments</h1>
<table>
<thead />
<tbody>
{commentsList.map(comment => (
<tr key={comment.id}>
<td style={{ border: "2px solid black" }}>
{comment.comment}
</td>
<td />
</tr>
))}
</tbody>
</table>
<br />
<br />
</Paper>
</div>
);
}
}
const mapStateToProps = (state, props) => {
return {
comments: state.comments,
comment: state.comment,
users: state.users === null ? null : state.users,
};
};
export default connect(
mapStateToProps,
{
getAllComments,
getComment,
}
)(DisplayComments);
My knowledge on React is limited since I just started learning about it
So I'm guessing it should be done with lifecyclehooks
but I dont really understand them yet.
this is the Parent component rendering the commment form and the displaying of the comments
class TicketDetails extends PureComponent {
componentWillMount() {
this.props.getAllComments();
}
addComment = comment => {
this.props.addComment(comment);
};
render() {
const { ticket, tickets, comments } = this.props;
const { users } = ticket;
return <div>
<Card className="outer-card">
<h1>Ticket: {ticket.id}</h1>
<h2>Price: €{ticket.price}</h2>
<p>Description: {ticket.description}</p>
<h2>Image: {ticket.image}</h2>
<p>Risk: {getRiskfactor(tickets, ticket, users, comments)} %</p>
<hr />
</Card>
<DisplayComments data={this.props} />
<CommentForm onSubmit={this.addComment} />
</div>;
}
}
const mapStateToProps = state => {
return {
ticket: state.ticket,
users: state.users,
tickets: state.tickets,
comments: state.comments
};
};
export default connect(
mapStateToProps,
{
addComment,
getAllComments
}
)(TicketDetails);
the TicketDetails is rendered in the App component
class App extends Component {
render() {
return (
<Router>
<div>
<TopBar />
<br/><br/>
<Route exact path="/login" component={LoginPage} />
<Route exact path="/logout" component={LogoutPage} />
<Route exact path="/signup" component={SignupPage} />
<Route exact path="/events" component={EventsList} />
<Route exact path="/addEvent" component={AddEvent} />
<Route exact path="/events/:id" component={TicketsList} />
<Route exact path="/tickets/:id" component={TicketDetails} />
<Route exact path="/addTicket" component={AddTicket} />
<Route exact path="/" render={() => <Redirect to="/events" />} />
</div>
</Router>
);
}
}
Action/comments.js
import * as request from "superagent";
import {baseUrl} from "../constants";
import {logout} from "./users";
import {isExpired} from "../jwt";
export const GET_ALL_COMMENTS = "GET_ALL_COMMENTS";
export const GET_COMMENT = "GET_COMMENT";
export const ADD_COMMENT = "ADD_COMMENT";
export const getAllComments = () => (dispatch, getState) => {
const state = getState();
if (!state.currentUser) return null;
const jwt = state.currentUser.jwt;
if (isExpired(jwt)) return dispatch(logout());
request
.get(`${baseUrl}/comments`)
.set("Authorization", `Bearer ${jwt}`)
.then(response =>
dispatch({
type: GET_ALL_COMMENTS,
payload: response.body.comments
})
)
.catch(err => alert(err));
};
export const getComment = commentId => (dispatch, getState) => {
const state = getState();
if (!state.currentUser) return null;
const jwt = state.currentUser.jwt;
if (isExpired(jwt)) return dispatch(logout());
request
.get(`${baseUrl}/comments/${commentId}`)
.set("Authorization", `Bearer ${jwt}`)
.then(response =>
dispatch({
type: GET_COMMENT,
payload: response.body
})
)
.catch(err => alert(err));
};
export const addComment = comment => (dispatch, getState) => {
const state = getState();
const jwt = state.currentUser.jwt;
if (isExpired(jwt)) return dispatch(logout());
request
.post(`${baseUrl}/comments`)
.set("Authorization", `Bearer ${jwt}`)
.send({
comment: comment.comment,
tickets: state.ticket
})
.then(response =>
dispatch({
type: ADD_COMMENT,
payload: response.body
})
);
};
reducers/comments.js
import { GET_ALL_COMMENTS } from "../actions/comments";
export default function(state = [], action) {
switch (action.type) {
case GET_ALL_COMMENTS:
return action.payload;
default:
return state;
}
}
reducers/comment.js
import { GET_COMMENT,ADD_COMMENT } from "../actions/comments";
const comment = {};
export default function(state = comment, action) {
switch (action.type) {
case GET_COMMENT:
return action.payload;
case ADD_COMMENT:
return action.payload;
default:
return state;
}
}
Although your question seems to be incomplete, but still there are few mistakes in implementation.
You have not used your dispatch to props properly. It is meant to connect dispatch to your function so that you donot have to call this.props.dispatch(func()) explicitly in your function
const mapDispatchToProps = (dispatch) => {
return {
addComment: (comment) => dispatch(addComment(comment))
}
}
It doesnot seems a good practice to pass your props down to two to three level. Since your class is statful class you can easily get it in your component and even call it with quite a ease.
Why would one have a action to get data from store. Actions and reducers are meant to be making changes in store like add, deleting and updating, not for fetching data. For fetching data you are connecting state to props.
UPDATE****
// Add import of the action here like import {addComment} from '../.whatever';
class TicketDetails extends PureComponent {
componentWillMount() {
this.props.getAllComments();
}
addComment = comment => {
this.props.addComment(comment);
};
render() {
const { ticket, tickets, comments } = this.props;
const { users } = ticket;
return <div>
<Card className="outer-card">
<h1>Ticket: {ticket.id}</h1>
<h2>Price: €{ticket.price}</h2>
<p>Description: {ticket.description}</p>
<h2>Image: {ticket.image}</h2>
<p>Risk: {getRiskfactor(tickets, ticket, users, comments)} %</p>
<hr />
</Card>
<DisplayComments data={this.props} />
<CommentForm onSubmit={this.addComment} />
</div>;
}
}
const mapStateToProps = state => {
return {
ticket: state.ticket,
users: state.users,
tickets: state.tickets,
comments: state.comments
};
};
const mapDispatchToProps = (dispatch) => {
return {
addComment: (comment) => dispatch(addComment(comment)),
getAllComments: () => dispatch(getAllComments())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(TicketDetails);

search component with react/redux/router flow

I'm trying to complete my app, have learned react, redux, react router all in one, now I'm just confused a bit when it comes to putting it all together.
Say I have a Nav component that's included in a header that's included globally on all pages and it calls a redux action which then runs a reducer and returns some search results.
When one searches from the navigation bar, how do I get it to redirect a search page that then returns the search results?
Nav component
class Nav extends React.Component {
render() {
const { search } = this.props;
return (
<header>
<SearchBox search={search} />
</header>
)
}
}
that includes a search component
class SearchBox extends React.Component {
constructor() {
super();
this.state = {
name: ''
}
}
handleChange = event => {
this.setState({
[event.target.id]: event.target.value
});
}
handleSubmit = event => {
event.preventDefault();
this.props.search(JSON.stringify({name: this.state.name}))
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" id="name" onChange={this.handleChange} placeholder="Search" />
<button type="submit">Search</button>
</form>
)
}
}
my layouts are like
index.js
const Index = () => {
return (
<Nav />
... Home Content
)
}
profile.js
const Profile = () => {
return (
<Nav />
... Profile Content
)
}
search.js
const Users = (props) => {
let list = null;
list = props.users.map((user, index)=> {
const { name } = user.profile;
const { username } = user;
return (
<li key={index}>
<h3><a href={'/'+username}>{name}</a></h3>
</li>
)
});
return <section id="search"><ul>{list}</ul></section>;
}
class Search extends React.Component {
render() {
const { searchResults } = this.props;
return (
<Nav />
<div>
{
/* show 'No results when no results found' */
searchResults !== ''
? seachResults.length == 0
? 'No results found'
: <Users users={searchResults} />
: null
}
</div>
)
}
}
const mapStateToProps = state => ({
searchResults: state.store.searchResults,
});
the user action is
export const search = (name) => dispatch => {
...
dispatch({
type: SEARCH_USER,
payload: res
})
the reducer is
const initialState = {
searchResults: ''
};
case SEARCH_USER:
return {
...state,
searchResults: action.payload.search
}
}
index.js
class App extends React.Component {
render() {
return (
<Router>
<Switch>
<Route path="/" exact={true} component={Index} />
<Route path="/profile" component={Profile} />
<Route path="/search" component={Search} />
</Switch>
</Router>
)
}
}

Resources