Is react-router-dom have browser history in reactjs? - reactjs

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

Related

Pass user id via props in React

I'm new to React. I have App.jsx:
import React, { Component } from 'react';
import { Route, Switch } from 'react-router-dom';
import axios from 'axios';
import About from './components/About';
import NavBar from './components/NavBar';
import Form from './components/forms/Form';
import Logout from './components/Logout';
import UserStatus from './components/UserStatus';
import Seeds from './components/Seeds';
import Message from './components/Message';
class App extends Component {
constructor() {
super();
this.state = {
users: [],
title: 'Project',
isAuthenticated: false,
messageName: null,
messageType: null,
};
this.logoutUser = this.logoutUser.bind(this);
this.loginUser = this.loginUser.bind(this);
this.createMessage = this.createMessage.bind(this);
this.removeMessage = this.removeMessage.bind(this);
};
componentWillMount() {
if (window.localStorage.getItem('authToken')) {
this.setState({ isAuthenticated: true });
};
};
componentDidMount() {
this.getUsers();
};
getUsers() {
axios.get(`${process.env.REACT_APP_WEB_SERVICE_URL}/users`)
.then((res) => { this.setState({ users: res.data.data.users }); })
.catch((err) => { });
};
logoutUser() {
window.localStorage.clear();
this.setState({ isAuthenticated: false });
};
loginUser(token) {
window.localStorage.setItem('authToken', token);
this.setState({ isAuthenticated: true });
this.getUsers();
this.createMessage('Welcome', 'success');
};
createMessage(name='Sanity Check', type='success') {
this.setState({
messageName: name,
messageType: type
});
setTimeout(() => {
this.removeMessage();
}, 3000);
};
removeMessage() {
this.setState({
messageName: null,
messageType: null
});
};
render() {
return (
<div>
<NavBar
title={this.state.title}
isAuthenticated={this.state.isAuthenticated}
/>
<section className="section">
<div className="container">
{this.state.messageName && this.state.messageType &&
<Message
messageName={this.state.messageName}
messageType={this.state.messageType}
removeMessage={this.removeMessage}
/>
}
<div className="columns">
<div className="column is-half">
<br/>
<Switch>
<Route exact path='/' render={() => (
<SpotifyAuth
/>
)} />
<Route exact path='/about' component={About}/>
<Route exact path='/register' render={() => (
<Form
formType={'Register'}
isAuthenticated={this.state.isAuthenticated}
loginUser={this.loginUser}
createMessage={this.createMessage}
/>
)} />
<Route exact path='/login' render={() => (
<Form
formType={'Login'}
isAuthenticated={this.state.isAuthenticated}
loginUser={this.loginUser}
createMessage={this.createMessage}
/>
)} />
<Route exact path='/logout' render={() => (
<Logout
logoutUser={this.logoutUser}
isAuthenticated={this.state.isAuthenticated}
/>
)} />
<Route exact path='/status' render={() => (
<UserStatus
isAuthenticated={this.state.isAuthenticated}
/>
)} />
<Route exact path='/seeds' render={() => (
<Seeds
isAuthenticated={this.state.isAuthenticated}
/>
)} />
</Switch>
</div>
</div>
</div>
</section>
</div>
)
}
};
export default App;
this Form.jsx component:
import React, { Component } from 'react';
import axios from 'axios';
import { Redirect } from 'react-router-dom';
import { registerFormRules, loginFormRules, spotifyLoginFormRules } from './form-rules.js';
import FormErrors from './FormErrors.jsx';
class Form extends Component {
constructor (props) {
super(props);
this.state = {
formData: {
restaurant: '',
username: '',
email: '',
password: ''
},
registerFormRules: registerFormRules,
loginFormRules: loginFormRules,
valid: false,
};
this.handleUserFormSubmit = this.handleUserFormSubmit.bind(this);
componentDidMount() {
this.clearForm();
};
componentWillReceiveProps(nextProps) {
if (this.props.formType !== nextProps.formType) {
this.clearForm();
};
};
clearForm() {
this.setState({
formData: {restaurant: '', username: '', email: '', password: ''}
});
};
handleFormChange(event) {
const obj = this.state.formData;
obj[event.target.name] = event.target.value;
this.setState(obj);
this.validateForm();
};
handleUserFormSubmit(event) {
event.preventDefault();
const formType = this.props.formType
const data = {
restaurant: this.state.formData.restaurant,
email: this.state.formData.email,
password: this.state.formData.password
};
if (formType === 'Register') {
data.username = this.state.formData.username
};
const url = `${process.env.REACT_APP_WEB_SERVICE_URL}/auth/${formType.toLowerCase()}`;
axios.post(url, data)
.then((res) => {
this.clearForm();
this.props.loginUser(res.data.auth_token);
})
.catch((err) => {
if (formType === 'Login') {
this.props.createMessage('Login failed.', 'danger');
};
if (formType === 'Register') {
this.props.createMessage('That user already exists.', 'danger');
};
});
};
allTrue() {
let formRules = loginFormRules;
if (this.props.formType === 'Register') {
formRules = registerFormRules;
}
for (const rule of formRules) {
if (!rule.valid) return false;
}
return true;
};
resetRules() {
const registerFormRules = this.state.registerFormRules;
for (const rule of registerFormRules) {
rule.valid = false;
}
this.setState({registerFormRules: registerFormRules})
const loginFormRules = this.state.loginFormRules;
for (const rule of loginFormRules) {
rule.valid = false;
}
this.setState({loginFormRules: loginFormRules})
};
validateEmail(email) {
// eslint-disable-next-line
var re = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
};
render() {
if (this.props.isAuthenticated) {
return (<Redirect to='/' />);
};
let formRules = this.state.loginFormRules;
if (this.props.formType === 'Register') {
formRules = this.state.registerFormRules;
}
return (
<div>
{this.props.formType === 'Login' &&
<h1 className="title is-1">Log In</h1>
}
{this.props.formType === 'Register' &&
<h1 className="title is-1">Register</h1>
}
<hr/><br/>
<FormErrors
formType={this.props.formType}
formRules={formRules}
/>
<form onSubmit={(event) => this.handleUserFormSubmit(event)}>
{this.props.formType === 'Register' &&
<div className="field">
<input
name="restaurant"
className="input is-medium"
type="text"
placeholder="Enter your restaurant name"
required
value={this.state.formData.coffeeshop}
onChange={this.handleFormChange}
/>
</div>
}
<div className="field">
<input
name="username"
className="input is-medium"
type="text"
placeholder="Enter a username"
required
value={this.state.formData.username}
onChange={this.handleFormChange}
/>
</div>
<div className="field">
<input
name="email"
className="input is-medium"
type="email"
placeholder="Enter an email address"
required
value={this.state.formData.email}
onChange={this.handleFormChange}
/>
</div>
<div className="field">
<input
name="password"
className="input is-medium"
type="password"
placeholder="Enter a password"
required
value={this.state.formData.password}
onChange={this.handleFormChange}
/>
</div>
<input
type="submit"
className="button is-primary is-medium is-fullwidth"
value="Submit"
disabled={!this.state.valid}
/>
</form>
</div>
)
};
};
export default Form;
and UserStatus.jsx
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';
import './css/box.css'
class UserStatus extends Component {
constructor (props) {
super(props);
this.state = {
restaurant:'',
email: '',
id: '',
username: '',
active: '',
admin: ''
};
};
componentDidMount() {
if (this.props.isAuthenticated) {
this.getUserStatus();
}
};
getUserStatus(event) {
const options = {
url: `${process.env.REACT_APP_WEB_SERVICE_URL}/auth/status`,
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`
}
};
return axios(options)
.then((res) => {
console.log(res.data)
console.log(res.data.data)
this.setState({
restaurant: res.data.data.restaurant,
email: res.data.data.email,
id: res.data.data.id,
username: res.data.data.username,
active: String(res.data.data.active),
admin: String(res.data.data.admin),
})
})
.catch((error) => { console.log(error); });
};
render() {
if (!this.props.isAuthenticated) {
return (
<p>You must be logged in to view this. Click <Link to="/login">here</Link> to log back in.</p>
)
};
return (
<div>
<h1 className="title is-1"><font color="#C86428">Current User</font></h1>
<hr/><br/>
<ul>
<li><strong><font color="#C86428">restaurant:</font></strong><font color="white"> {this.state.restaurant} </font></li>
<li><strong><font color="#C86428">User ID:</font></strong><font color="white"> {this.state.id} </font></li>
<li><strong><font color="#C86428">Email:</font></strong><font color="white"> {this.state.email} </font></li>
<li><strong><font color="#C86428">Username:</font></strong><font color="white"> {this.state.username} </font></li>
<li><strong><font color="#C86428">Active:</font></strong><font color="white"> {this.state.active} </font></li>
<li><strong><font color="#C86428">Admin:</font></strong><font color="white"> {this.state.admin} </font></li>
</ul>
</div>
)
};
};
export default UserStatus;
finally, this Seeds.jsx, where I need user.id in order to call my backend, like so:
import React, { Component } from 'react';
import axios from 'axios';
class Seeds extends Component{
constructor (props) {
super(props);
this.state = {
restaurant:'',
email: '',
id: '',
username: '',
active: '',
admin: '',
template:'',
formSeeds:{
type1:'',
type2:'',
type3:'',
},
formEditMenu:{
item:'',
item2:'',
item3:'',
}
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmitSeeds = this.handleSubmitSeeds.bind(this);
this.handleSubmitCoffees = this.handleSubmitCoffees.bind(this);
};
componentDidMount() {
if (this.props.isAuthenticated) {
this.getSeeds();
}
};
getSeeds(event) {
const options = {
url: `${process.env.REACT_APP_WEB_SERVICE_URL}/seeds/${this.props.getUserStatus.id}`, //HERE
method: 'get',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${window.localStorage.authToken}`
}
};
return axios(options)
.then((res) => {
console.log(res.data.data)
this.setState({
restaurant: res.data.data[0].restaurant,
id: res.data.data[0].id,
template: res.data.data[0].content
})
console.log(res.data.data[0].id);
})
.catch((error) => { console.log(error); });
};
handleChange(event){
const objformSeeds = this.state.formSeeds;
objformSeeds[event.target.name] = event.target.value;
this.setState(objformSeeds);
const formEditMenu = this.state.formEditMenu;
formEditMenu[event.target.name] = event.target.value;
this.setState(formEditMenu);
}
handleSubmitSeeds(event) {
event.preventDefault();
//const formSeeds = this.props.formSeeds
const data = {
type1: this.state.formSeeds.type1,
type2: this.state.formSeeds.type2,
type3: this.state.formSeeds.type3,
};
const url = `${process.env.REACT_APP_WEB_SERVICE_URL}/handle_seeds`;
axios.post(url, data)
.then((res) => {
console.log(data);
})
.catch((err) => {
});
};
handleSubmitCoffees(event) {
event.preventDefault();
//const formEditMenu = this.props.formEditMenu
const data = {
item: this.state.formEditMenu.item,
item2: this.state.formEditMenu.item2,
item3: this.state.formEditMenu.item3,
};
const url = `${process.env.REACT_APP_WEB_SERVICE_URL}/edit_menu${this.props.getUserStatus.id}`; //AND HERE
axios.post(url, data)
.then((res) => {
console.log(data);
})
.catch((err) => {
});
};
render(){
//var seeds_page = this.state.template;
var __html = this.state.template;
var template = { __html: __html };
return (
<div id="parent">
<div dangerouslySetInnerHTML={template}/>
<form>
<input type='text' name='name' onChange={this.handleChange}/><br/>
<button type="button" onClick={this.handleSubmitCoffees} />
</form>
</div>
);
}
}
export default Seeds;
I am trying to get id via props (this.props.getUserStatus.id), but I get undefined. How do I pass my user.id to Seeds.jsx component here?
Well, first of all you may want to use a store like redux for state management. If you really want to keep your state locally, then you should "lift it", and move your getUserStatus() function in the highest possible parent (in your case App.js). In your App.js you then set a userStatus object in the state and pass it down to all components which need it.
<Route exact path='/seed' render={() => (
<Seed
....
userStatus={this.state.userStatus}
/>
)} />
and in your child component:
....
render(){
const { userStatus } = this.props
return(
......
const url = `${process.env.REACT_APP_WEB_SERVICE_URL}/edit_menu${userStatus.id}`;
)
}
The only property you passed to Seeds is isAuthenticated.

Reducer does not call render function

I'm try to learn and develop React Redux app. In the app I have some private routes. If the user goes to the private routes he should be authenticated with LogIn component and then redirected to the initial route.
The problem is that after the user submits the form and gets authenticated, the reducer doesn't call the render method of LogIn component.
I'm stuck and can't figure out the reason of this.
// ../ClientApp/src/App.js
import React from 'react';
import { Route } from 'react-router';
import { Redirect } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './components/Home';
import Block from './components/Block';
import LogIn from './components/LogIn';
export const auth = {
isAuthenticated: false
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
auth.isAuthenticated
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)} />
)
export default () => (
<Layout>
<Route exact path='/' component={Home} />
<PrivateRoute path='/block1' component={Block} />
<PrivateRoute path='/block2' component={Block} />
<PrivateRoute path='/block3' component={Block} />
<PrivateRoute path='/block4' component={Block} />
<PrivateRoute path='/block5' component={Block} />
<PrivateRoute path='/block7' component={Block} />
<Route path='/login' component={LogIn} />
</Layout>
);
// ../ClientApp/src/components/LogIn.js
import React, { Component } from 'react';
import { connect } from "react-redux";
import { bindActionCreators } from 'redux';
import './LogIn.css';
import { actionCreators } from '../store/LogIn';
import { Redirect } from 'react-router-dom';
import { auth } from '../App';
class LogIn extends Component {
state = {
credentials: {
username: '',
password: ''
},
error: ''
}
dismissError = () => {
this.setState({ error: '' });
}
handleChange = e => {
const credentials = this.state.credentials;
credentials[e.target.name] = e.target.value;
this.setState({ credentials: credentials });
}
handleSubmit = (e) => {
e.preventDefault();
if (!this.state.credentials.username) {
return this.setState({ error: 'This field is required' });
}
if (!this.state.credentials.password) {
return this.setState({ error: 'This field is required' });
}
this.props.requestLogIn(this.state.credentials);
}
render() {
auth.isAuthenticated = this.props.isAuthenticated;
const { credentials } = this.state;
if (this.props.redirectToReferrer) {
const { from } = this.props.location.state || {
from: { pathname: '/' }
}
return (
<Redirect to={from} />
)
}
return (
<div className="container">
<div className="row">
<div className="col-md-6 col-md-offset-3">
<div className="panel panel-login">
<div className="panel-heading">
<div className="row">
<div className="col-xs-6">
Log in
</div>
</div>
<hr />
</div>
<div className="panel-body">
<div className="row">
<div className="col-lg-12">
<form id="login-form" onSubmit={this.handleSubmit} style={{ display: 'block' }}>
{
this.state.error &&
<h3 data-test="error" onClick={this.dismissError}>
<button onClick={this.dismissError}>X</button>
{this.state.error}
</h3>
}
<div className="form-group">
<input
type="text"
name="username"
tabIndex="1"
className="form-control"
placeholder="E-mail"
value={credentials.username}
onChange={this.handleChange} />
</div>
<div className="form-group">
<input
type="password"
name="password"
tabIndex="2"
className="form-control"
placeholder="Password"
value={credentials.password}
onChange={this.handleChange} />
</div>
<div className="form-group">
<div className="row">
<div className="col-sm-6 col-sm-offset-3">
<input
type="submit"
tabIndex="3"
className="form-control btn btn-login"
value="Log in" />
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
isAuthenticated: state.isAuthenticated,
redirectToReferrer: state.redirectToReferrer
}
}
export default connect(
mapStateToProps,
dispatch => bindActionCreators(actionCreators, dispatch)
)(LogIn);
// ../ClientApp/src/store/LogIn.js
const authenticated = 'AUTHENTICATED_USER';
const unauthenticated = 'UNAUTHENTICATED_USER';
const authenticationError = 'AUTHENTICATION_ERROR';
const initialState = {
isAuthenticated: false,
redirectToReferrer: false,
error: '',
token: ''
}
export const actionCreators = {
requestLogIn: ({ username, password }) => async (dispatch) => {
try {
const response = await fetch('api/Authentication/Authenticate',
{
method: 'POST',
body: JSON.stringify({
username: username,
password: password
}),
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin'
});
const token = await response.text();
dispatch({
type: authenticated,
token
});
} catch (e) {
console.log(e);
dispatch({
type: authenticationError,
error: 'Invalid email or password'
});
}
}
}
export const reducer = (state, action) => {
state = state || initialState;
switch (action.type) {
case authenticated:
return {
...state,
isAuthenticated: true,
redirectToReferrer: true,
token: action.token
};
case unauthenticated:
return { ...state, isAuthenticated: false };
case authenticationError:
return { ...state, isAuthenticated: false, error: action.error };
}
return state;
}
UPDATE:
Thanks to remix23's answer. He was right that I had several reducers and I had to point logIn reducer in the mapStateToProps function like this:
const mapStateToProps = state => {
return {
isAuthenticated: state.logIn.isAuthenticated,
redirectToReferrer: state.logIn.redirectToReferrer,
error: state.logIn.error,
token: state.logIn.token
}
}
Just for your information (perhaps it can be usefull for someone) here's my reducer configuration:
//.. /ClientApp/src/store/configureStore.js:
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import * as Items from '../reducers/items';
import * as LogIn from './LogIn';
export default function configureStore(history, initialState) {
const reducers = {
items: Items.reducer,
logIn: LogIn.reducer
};
const middleware = [
thunk,
routerMiddleware(history)
];
// In development, use the browser's Redux dev tools extension if installed
const enhancers = [];
const isDevelopment = process.env.NODE_ENV === 'development';
if (isDevelopment && typeof window !== 'undefined' && window.devToolsExtension) {
enhancers.push(window.devToolsExtension());
}
const rootReducer = combineReducers({
...reducers,
routing: routerReducer
});
return createStore(
rootReducer,
initialState,
compose(applyMiddleware(...middleware), ...enhancers)
);
}
The path // ../ClientApp/src/store/LogIn.js suggests that you may have several reducers defined under the store folder.
This usually implies that you also have an "app" reducer (a combination of all your reducers with a key for each of them).
If that the case and the key for your login reducer is login, then in the mapStateToProps you provided, you may have to access the isAuthenticated value this way (otherwise state.isAuthenticated will stay undefined):
const mapStateToProps = state => {
return {
isAuthenticated: state.login.isAuthenticated,
...
}
}
Also as others have suggested, accessing auth from the initial value of your auth store is bad, even if as it looks it may work because you set it in the Login component.
You should connect App like you did with Login and access isAuthenticated through props (and never set a value of the initial states of your stores).

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

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