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);
Related
I am using this approach to make some routes in my application only accessible after log in.
So in App.js I have some ProtectedRoutes.
<ProtectedRoute path='/projects' auth={this.props} component={Projects} />
<ProtectedRoute path='/search' auth={this.props} component={Search} />
<ProtectedRoute path='/admin' auth={this.props} component={Admin} />
And my ProtectedRoute function is like this:
const ProtectedRoute = ({ component: Comp, auth, path, ...rest }) => {
const isAuthenticated = auth.isAuthenticated;
const sendprops = { ...auth, ...rest };
return (
<Route
path={path}
{...rest}
render={props => {
return isAuthenticated ? (
<Comp {...sendprops} />
) : (
<Redirect to='/' />
);
}}
/>
);
};
export default ProtectedRoute;
But having done that, I've totally broken my Search component, which relies on being able to see and push to the history property.
this.props.history.push({
pathname: this.props.history.location.pathname,
search: `?query=${ this.state.searchQuery}&search_type=${this.state.searchType}&page=${this.state.searchPage}`
});
When I try to console.log out the properties in the Search component, I can see location still, but I've lost history. So in what feels like a somewhat ham fisted, ignorant fashion, I've wrapped my Search export in withRouter.
export default withRouter(Search);
And now I can see the history again. But I have no idea why I lost it in the first place? Insights would be greatly appreciated.
As requested, here is the Search component.
import React, { Component } from 'react';
import ResultList from './SearchResultList';
import { withRouter } from 'react-router'
class Search extends Component {
constructor(props) {
super(props);
console.log(props);
let url_query = '';
let url_search_type = '';
let url_search_page = 1;
let loading = false;
if(this.props.location.search) {
const params = new URLSearchParams(this.props.location.search);
url_query = params.get('query');
url_search_type = params.get('search_type');
url_search_page = params.get('page') || 1;
loading = true;
}
this.state = {
searchQuery: url_query,
searchType: url_search_type,
searchPage: url_search_page,
searchResult: {
results: [],
pages_left: '',
pages_right: '',
page: '',
type: ''
},
isLoading: loading
}
}
componentDidMount() {
if(this.state.isLoading){
this.doSearch();
}
}
handleChange = (e) => {
const search_name = e.target.name;
const search_value = e.target.value.trim();
this.setState({
[search_name]: search_value,
})
}
handleSubmit = (e) => {
this.props.history.push({
pathname: this.props.history.location.pathname,
search: `?query=${ this.state.searchQuery }&search_type=${this.state.searchType}&page=${this.state.searchPage}`
});
this.setState({
isLoading: true,
searchPage: 1
}, this.doSearch);
}
changePage = (e) => {
this.props.history.push({
pathname: this.props.history.location.pathname,
search: `?query=${ this.state.searchQuery }&search_type=${this.state.searchType}&page=${this.state.searchPage}`
});
this.setState({
searchPage: e,
isLoading: true
}, this.doSearch);
}
doSearch = () => {
//console.log(this.state);
const endpoint = 'http://urlurlurllll/search?query=' + this.state.searchQuery + '&search_type=' + this.state.searchType + '&page=' + this.state.searchPage;
//console.log(endpoint);
fetch(endpoint)
.then(data => data.json())
.then(jdata => {
this.setState({
searchResult: {
results: jdata.results,
pages_left: jdata.pages_left,
pages_right: jdata.pages_right,
page: jdata.page,
type: this.state.searchType
},
isLoading: false
})
})
.catch(error => console.log(error));
}
render (){
const isLoading = this.state.isLoading;
const searchResult = this.state.searchResult;
return (
<React.Fragment>
<div>
<h1>Search</h1>
<form>
<label>Search Term:</label>
<input type="text" id="input-search" name="searchQuery" value={this.state.searchQuery} onChange={ event => this.handleChange(event) } />
<label>Search Type:</label>
<select id="select-type" name="searchType" value={this.state.searchType} onChange={ event => this.handleChange(event) }>
<option value=''>Select Type</option>
<option value='hashtag'>hashtag</option>
<option value='handle'>handle</option>
<option value='keyword'>keyword</option>
</select>
<button type="button" id="btn-search" onClick={event => this.handleSubmit(event)}>Search</button>
</form>
</div>
<div>
{ isLoading ? (
<div>
<p>Loading...</p>
</div>
) : (
<React.Fragment>
<h2>Search Results</h2>
{searchResult.results.length ? (
<ResultList searchResult={searchResult} changePage={this.changePage} />
) : (
<p>Use the little form up there to start searching!</p>
)}
</React.Fragment>
)}
</div>
</React.Fragment>
);
}
}
export default withRouter(Search);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.3.1/umd/react-dom.production.min.js"></script>
Issue
I think the issue is the route props from render aren't passed along to the component. (Though it is unclear to me how the location route prop was still working)
Solution
Spread the route props (match, location, and history) provided from render into the component being rendered.
const ProtectedRoute = ({ component: Comp, auth, path, ...rest }) => {
const isAuthenticated = auth.isAuthenticated;
const sendprops = { ...auth, ...rest };
return (
<Route
path={path}
{...rest}
render={(props) => // <-- route props need to be
isAuthenticated ? (
<Comp {...sendprops} {...props} /> // <-- passed to component
) : (
<Redirect to="/" />
)
}
/>
);
};
I just checked that tutorial/lesson you linked to see what they were doing and they pass the route props through as well.
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();
}
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>
)
}
}
Is browserHistory contains in react-router-dom?? If it is not then how I can redirect to a page using history.push?
Here is my code, where to add changes in my code?
LoginForm.js
class LoginForm extends Component {
constructor(props) {
super(props);
this.state = {};
this.onSubmit = this.onSubmit.bind(this);
}
render() {
let {username, password} = this.state;
let {isLoginPending, isLoginSuccess, loginError} = this.props;
return (
<div>
<Back/>
<header>
<h1>Company Login</h1>
</header>
<form name="loginForm" onSubmit={this.onSubmit}>
<div className="imgcontainer">
<img src={avatar} alt={"Avatar"} className={"avatar"}/>
</div>
<div className="form-group-collection">
<div className="form-group">
<label>Username/User ID:</label>
<input name="username" onChange={e => this.setState({username: e.target.value})} value={username}/>
</div>
<div className="form-group">
<label>Password:</label>
<input type="password" name="password" onChange={e => this.setState({password: e.target.value})} value={password}/>
</div>
</div>
<br/>
<input type="submit" value="Login" />
</form>
<footer>Copyright © multihands.com. </footer>
</div>
)
}
onSubmit(e) {
e.preventDefault();
let { username, password } = this.state;
this.props.login(username, password);
this.setState({
username: '',
password: ''
});
}
}
const mapStateToProps = (state) => {
return {
isLoginPending: state.isLoginPending,
isLoginSuccess: state.isLoginSuccess,
loginError: state.loginError
};
}
const mapDispatchToProps = (dispatch) => {
return {
login: (username, password) => dispatch(login(username, password))
};
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
Reducer.js
import logindetls from '../../logindet.json';
const SET_LOGIN_PENDING = 'SET_LOGIN_PENDING';
const SET_LOGIN_SUCCESS = 'SET_LOGIN_SUCCESS';
const SET_LOGIN_ERROR = 'SET_LOGIN_ERROR';
export function login(username, password) {
return dispatch => {
dispatch(setLoginPending(true));
dispatch(setLoginSuccess(false));
dispatch(setLoginError(null));
callLoginApi(username, password, error => {
dispatch(setLoginPending(false));
if (!error) {
dispatch(setLoginSuccess(true));
} else {
dispatch(setLoginError(error));
}
});
}
}
function setLoginPending(isLoginPending) {
return {
type: SET_LOGIN_PENDING,
isLoginPending
};
}
function setLoginSuccess(isLoginSuccess) {
return {
type: SET_LOGIN_SUCCESS,
isLoginSuccess
};
}
function setLoginError(loginError) {
return {
type: SET_LOGIN_ERROR,
loginError
}
}
function callLoginApi(username, password, callback) {
setTimeout(() => {
if (username === logindetls.username && password === logindetls.password)
{
return alert("Successfully Logged in...");
} else {
return alert('Invalid username and password');
}
}, 1000);
}
export default function reducer(state = {
isLoginSuccess: false,
isLoginPending: false,
loginError: null
}, action) {
switch (action.type) {
case SET_LOGIN_PENDING:
return Object.assign({}, state, {
isLoginPending: action.isLoginPending
});
case SET_LOGIN_SUCCESS:
return Object.assign({}, state, {
isLoginSuccess: action.isLoginSuccess
});
case SET_LOGIN_ERROR:
return Object.assign({}, state, {
loginError: action.loginError
});
default:
return state;
}
}
And I add redirecting page(userpage.js) to routes.js page
class Routes extends Component {
render() {
return (
<div>
<Router>
<Switch>
<Route exact path="/" component={HomePage}/>
<Route path="/about" component={About}/>
<Route path="/loginform" component={LoginForm}/>
<Route path="/companies" component={Companies}/>
<Route path="/services" component={Services}/>
<Route path="/contact" component={Contact}/>
<Route path="/userpage" component={UserPage}/>
</Switch>
</Router>
</div>
);
}
}
Where does I need to add my routing code and what is needed to import?? Is there any need to install react-router? Does React-router-dom haven't any property to push??In my code where will I add this routing function??
import { withRouter } from 'react-router-dom'
class LoginForm extends Component {
componentWillReceiveProps(nextProps) {
if (!nextProps.isLoginPending && this.props.isLoginPending) {
if (nextProps.isLoginSuccess) {
this.props.history.push('/path-to-redirect-on-login')
} else {
console.log(this.props.loginError)
}
}
}
render() {
...
}
}
...
LoginForm = withRouter(LoginForm);
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
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'));