How to make controlled input component in Redux? - reactjs

I'm implementing movie search functionality using the moviedb api. I have implemented in React only but I want to do it in Redux. Here is my approach in React.
Header.js
import React, { Component } from "react"
import { Navbar, Form, FormControl } from "react-bootstrap"
import { NavLink } from "react-router-dom"
import axios from "axios"
import MovieCards from "./MovieCards"
const apiKey = process.env.REACT_APP_MOVIE_DB_API_KEY
class Header extends Component {
state = {
isSearching: false,
value: "",
movies: []
}
searchMovies = async val => {
this.setState({ isSearching: true })
const res = await axios.get(
`https://api.themoviedb.org/3/search/movie?api_key=${apiKey}&language=en-US&query=${val}&page=1&include_adult=true`
)
const movies = await res.data.results
this.setState({ movies: movies, isSearching: false })
}
handleChange = e => {
const { name, value } = e.target
this.searchMovies(value)
this.setState({
[name]: value
})
}
render() {
return this.state.value === "" ? (
<div>
<Navbar
bg="dark"
expand="lg"
style={{ justifyContent: "space-around" }}
>
<NavLink to="/">
<Navbar.Brand>Movie Catalogue</Navbar.Brand>
</NavLink>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Form inline>
<FormControl
type="text"
placeholder="Search"
className="mr-sm-2"
onChange={this.handleChange}
name="value"
value={this.state.value}
/>
</Form>
</Navbar.Collapse>
<NavLink to="/popular">Popular</NavLink>
<NavLink to="/now-playing">Now Playing</NavLink>
<NavLink to="/top-rated">Top Rated</NavLink>
<NavLink to="/upcoming">Upcoming</NavLink>
</Navbar>
{this.state.movies.map((movie, i) => {
return <MovieCards key={i} movie={movie} />
})}
</div>
) : (
<div>
<Navbar
bg="dark"
expand="lg"
style={{ justifyContent: "space-around" }}
>
<NavLink to="/">
<Navbar.Brand>Movie Catalogue</Navbar.Brand>
</NavLink>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Form inline>
<FormControl
type="text"
placeholder="Search"
className="mr-sm-2"
onChange={this.handleChange}
name="value"
value={this.state.value}
/>
</Form>
</Navbar.Collapse>
<p style={{ color: "white" }}>
Search results for " {this.state.value} "
</p>
</Navbar>
{this.state.movies.map((movie, i) => {
return <MovieCards key={i} movie={movie} />
})}
</div>
)
}
}
export default Header
I want to do it using Redux, so I'm doing it this way.
Header.js
import React, { Component } from "react"
import { Navbar, Form, FormControl } from "react-bootstrap"
import { NavLink } from "react-router-dom"
import axios from "axios"
import { connect } from "react-redux"
import { movieSearch } from "../actions/index"
import MovieCards from "./MovieCards"
const apiKey = process.env.REACT_APP_MOVIE_DB_API_KEY
class Header extends Component {
handleChange = e => {
const { name, value } = e.target
this.props.dispatch(movieSearch(value)) // I'm not sure if this is the right approach. I'm dispatching and then setting state.
this.setState({
[name]: value
})
}
render() {
return this.state.value === "" ? (
<div>
<Navbar
bg="dark"
expand="lg"
style={{ justifyContent: "space-around" }}
>
<NavLink to="/">
<Navbar.Brand>Movie Catalogue</Navbar.Brand>
</NavLink>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Form inline>
<FormControl
type="text"
placeholder="Search"
className="mr-sm-2"
onChange={this.handleChange}
name="value"
value={this.state.value}
/>
</Form>
</Navbar.Collapse>
<NavLink to="/popular">Popular</NavLink>
<NavLink to="/now-playing">Now Playing</NavLink>
<NavLink to="/top-rated">Top Rated</NavLink>
<NavLink to="/upcoming">Upcoming</NavLink>
</Navbar>
{this.state.movies.map((movie, i) => {
return <MovieCards key={i} movie={movie} />
})}
</div>
) : (
<div>
<Navbar
bg="dark"
expand="lg"
style={{ justifyContent: "space-around" }}
>
<NavLink to="/">
<Navbar.Brand>Movie Catalogue</Navbar.Brand>
</NavLink>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Form inline>
<FormControl
type="text"
placeholder="Search"
className="mr-sm-2"
onChange={this.handleChange}
name="value"
value={this.state.value}
/>
</Form>
</Navbar.Collapse>
<p style={{ color: "white" }}>
Search results for " {this.state.value} "
</p>
</Navbar>
{this.state.movies.map((movie, i) => {
return <MovieCards key={i} movie={movie} />
})}
</div>
)
}
}
const mapStateToProps = (state) => {
return state
}
export default connect(mapStateToProps)(Header)
actions/index.js
export const movieSearch = val => {
const movieSearchUrl = `https://api.themoviedb.org/3/search/movie?api_key=${apiKey}&language=en-US&query=${val}&page=1&include_adult=true`
return async dispatch => {
dispatch({ type: "SEARCHING_MOVIES_START" })
try {
const res = await axios.get(movieSearchUrl)
dispatch({
type: "SEARCHING_MOVIES_SUCCESS",
data: { searchResults: res.data.results }
})
} catch (err) {
dispatch({
type: "SEARCHING_MOVIES_FAILURE",
data: { error: "Could not find the movie" }
})
}
}
}
reducers/movieSearchReducer.js
const initialState = {
value: "",
isSearchingMovies: false,
isSearchedMovies: false,
movieList: [],
searchingError: null
}
export const movieSearchReducer = (state = initialState, action) => {
switch (action.type) {
case "SEARCHING_MOVIES_START":
return {
...state,
isSearchingMovies: true,
searchingError: null
}
case "SEARCHING_MOVIES_SUCCESS":
return {
...state,
isSearchingMovies: false,
isSearchedMovies: true,
movieList: action.data,
searchingError: null
}
case "SEARCHING_MOVIES_FAILURE":
return {
...state,
searchingError: action.data.error
}
}
}
I'm not sure how to implement the part of the below input form part in Redux. Please help if you can.
onChange={this.handleChange}
name="value"
value={this.state.value}

When you change from state in component to redux, you will generally remove the react state and pickup the redux state from the 'props'.
So step 1 is to get rid of your setState all together.
value={this.state.value}
will become
value={this.props.movieList}
In order to get the movieList in the props, you need to wrap your component in a 'container' and use mapStateToProps to map the redux state to your props.
See https://react-redux.js.org/api/connect for more details

If you use Redux to store the movies, you can delete the local state to your component, and use redux's movieList prop instead.

Related

I must add login to navbar in React

I must add login system to navbar, but when I login in anywhere, navbar does not appear. How can I solve it?
Navbar shows normally with no login system.
Navbar is on App.js.
authLinks - Section just for authenticated users (Dashboard and Log out buttons)
guestLinks - Section just for not-authenticated users (Login form in here)
My navbar + login:
import React, { Fragment, useState } from "react";
import { Link, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { logout } from "../../actions/auth";
import { login } from "../../actions/auth";
const Navbar = ({ auth: { isAuthenticated, loading }, login, logout }) => {
const [formData, setFormData] = useState({
email: "",
password: ""
});
const { email, password } = formData;
const onChange = e =>
setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmit = async e => {
e.preventDefault();
login(email, password);
};
if (isAuthenticated) {
return <Redirect to="/dashboard" />;
}
const authLinks = (
<ul>
<li>
<Link to="/dashboard">
<i className="fas fa-user" /> <span className="hide-sm">Dashboard</span>
</Link>
</li>
<li>
<a onClick={logout} href="#!">
<i className="fas fa-sign-out-alt" />{" "}
<span className="hide-sm">Log out</span>
</a>
</li>
</ul>
);
const guestLinks = (
<form
className="form-inline my-2 my-lg-0 navbar-login"
onSubmit={e => onSubmit(e)}
>
<div className="form-group">
<input
className="form-control mr-sm-2"
type="email"
placeholder="Email Address"
name="email"
value={email}
onChange={e => onChange(e)}
/>
</div>
<div className="form-group">
<input
className="form-control mr-sm-2 my-sm-0"
type="password"
placeholder="Password"
name="password"
value={password}
onChange={e => onChange(e)}
/>
</div>
<input type="submit" className="btn btn-primary" value="Login" />
</form>
);
return (
<nav className="navbar navbar-expand-lg navbar-dark bg-dark">
<h1>
<Link className="navbar-brand" to="/">
<i className="fas fa-crown"></i>
<br />
nawdic
</Link>
</h1>
{!loading && (
<Fragment>{isAuthenticated ? authLinks : guestLinks}</Fragment>
)}
</nav>
);
};
Navbar.propTypes = {
logout: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
login: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool
};
const mapStateToProps = state => ({
auth: state.auth,
isAuthenticated: state.auth.isAuthenticated
});
export default connect(mapStateToProps, { login, logout })(Navbar);
When user is logged in Navbar returns redirect component which is not the navbar itself
if (isAuthenticated) {
return <Redirect to="/dashboard" />;
}
Since isAuthenticated is passed to Navbar it shouldn't be responsible for redirecting in this case.
Try to move the Redirect thing to router as a best practice

Can't get jest test to fire button onclick

I am completely new to both react and testing and am a bit stumped.
I was just wondering if someone could tell me why my test fails. I assume I making a basic mistake in how this should work.
I am trying to test a log in page. At the moment I am just trying to get my test to fire an onclick from a button and check that that a function has been called.
The log in component code can be seen below.
import React, { Component, Fragment } from "react";
import { Redirect } from "react-router-dom";
// Resources
//import logo from "assets/img/white-logo.png";
//import "./Login.css";
// Material UI
import {
withStyles,
MuiThemeProvider,
createMuiTheme
} from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import TextField from "#material-ui/core/TextField";
import Person from "#material-ui/icons/Person";
import InputAdornment from "#material-ui/core/InputAdornment";
// Custom Components
import Loading from "components/Loading/Loading.jsx";
// bootstrat 1.0
import { Alert, Row } from "react-bootstrap";
// MUI Icons
import LockOpen from "#material-ui/icons/LockOpen";
// remove
import axios from "axios";
// API
import api2 from "../../helpers/api2";
const styles = theme => ({
icon: {
color: "#fff"
},
cssUnderline: {
color: "#fff",
borderBottom: "#fff",
borderBottomColor: "#fff",
"&:after": {
borderBottomColor: "#fff",
borderBottom: "#fff"
},
"&:before": {
borderBottomColor: "#fff",
borderBottom: "#fff"
}
}
});
const theme = createMuiTheme({
palette: {
primary: { main: "#fff" }
}
});
class Login extends Component {
constructor(props, context) {
super(props, context);
this.state = {
username: "",
password: "",
isAuthenticated: false,
error: false,
toggle: true,
// loading
loading: false
};
}
openLoading = () => {
this.setState({ loading: true });
};
stopLoading = () => {
this.setState({ loading: false });
};
toggleMode = () => {
this.setState({ toggle: !this.state.toggle });
};
handleReset = e => {
const { username } = this.state;
this.openLoading();
api2
.post("auth/admin/forgotPassword", { email: username })
.then(resp => {
this.stopLoading();
console.log(resp);
})
.catch(error => {
this.stopLoading();
console.error(error);
});
};
handleSubmit = event => {
event.preventDefault();
localStorage.clear();
const cred = {
username: this.state.username,
password: this.state.password
};
api2
.post("auth/admin", cred)
.then(resp => {
console.log(resp);
localStorage.setItem("api_key", resp.data.api_key);
localStorage.setItem("username", cred.username);
return this.setState({ isAuthenticated: true });
})
.catch(error => {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log("Error", error.message);
}
console.log(error.config);
return this.setState({ error: true });
});
};
handleInputChange = event => {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
};
forgotPassword = () => {
console.log("object");
};
render() {
const { error } = this.state;
const { isAuthenticated } = this.state;
const { classes } = this.props;
if (isAuthenticated) {
return <Redirect to="/home/dashboard" />;
}
return (
<div className="login-page">
<video autoPlay muted loop id="myVideo">
<source
src=""
type="video/mp4"
/>
</video>
<div className="videoOver" />
<div className="midl">
<Row className="d-flex justify-content-center">
<img src={''} className="Login-logo" alt="logo" />
</Row>
<br />
<Row className="d-flex justify-content-center">
{error && (
<Alert style={{ color: "#fff" }}>
The username/password entered is incorrect. Try again!
</Alert>
)}
</Row>
<MuiThemeProvider theme={theme}>
<Row className="d-flex justify-content-center">
<TextField
id="input-username"
name="username"
type="text"
label="username"
value={this.state.username}
onChange={this.handleInputChange}
InputProps={{
className: classes.icon,
startAdornment: (
<InputAdornment position="start">
<Person className={classes.icon} />
</InputAdornment>
)
}}
/>
</Row>
{this.state.toggle ? (
<Fragment>
<br />
<Row className="d-flex justify-content-center">
<TextField
id="input-password"
name="password"
type="password"
label="pasword"
value={this.state.password}
onChange={this.handleInputChange}
className={classes.cssUnderline}
InputProps={{
className: classes.icon,
startAdornment: (
<InputAdornment position="start">
<LockOpen className={classes.icon} />
</InputAdornment>
)
}}
/>
</Row>
</Fragment>
) : (
""
)}
</MuiThemeProvider>
<br />
<Row className="d-flex justify-content-center">
{this.state.toggle ? (
<Button
className="button login-button"
data-testid='submit'
type="submit"
variant="contained"
color="primary"
onClick={this.handleSubmit}
name = "logIn"
>
Login
</Button>
) : (
<Button
className="button login-button"
type="submit"
variant="contained"
color="primary"
onClick={this.handleReset}
>
Reset
</Button>
)}
</Row>
<Row className="d-flex justify-content-center">
<p onClick={this.toggleMode} className="text-link">
{this.state.toggle ? "Forgot password?" : "Login"}
</p>
</Row>
</div>
<Loading open={this.state.loading} onClose={this.handleClose} />
</div>
);
}
}
export default withStyles(styles)(Login);
My current test which fails.
import React from 'react'
import {render, fireEvent, getByTestId} from 'react-testing-library'
import Login from './login'
describe('<MyComponent />', () => {
it('Function should be called once', () => {
const functionCalled = jest.fn()
const {getByTestId} = render(<Login handleSubmit={functionCalled} />)
const button = getByTestId('submit');
fireEvent.click(button);
expect(functionCalled).toHaveBeenCalledTimes(1)
});
});
I'm also fairly new to React Testing Library, but it looks like your button is using this.handleSubmit, so passing your mock function as a prop won't do anything. If you were to pass handleSubmit in from some container, then I believe your tests, as you currently have them, would pass.

i need to change the login to add to cart when the user login in react

is their any possible way to change the state of header that track the user is logged in or not and show the button as per it if login in then show add to cart and if not show login
i have checked the session storage where i have stored my data if their is data change the state if not don't in component did mount but the component did mount will not re render the app we have to do manual refresh how to keep track of it
import React from 'react';
import { NavLink } from 'react-router-dom';
import { Navbar, Nav, NavItem } from 'react-bootstrap';
import { LinkContainer } from 'react-router-bootstrap';
class Header extends React.Component {
state = {
login: false
}
componentDidMount() {
const data = sessionStorage.getItem("login");
if (data) {
this.setState({ login: !this.state.login })
}
}
componentDidUpdate(prevProps, prevState) {
if (prevState.login !== this.state.login) {
this.setState({ login: true })
}
}
render() {
return (
<Navbar inverse collapseOnSelect>
<Navbar.Header>
<Navbar.Brand>
<NavLink to="/" >Home</NavLink>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
<Navbar.Collapse>
<Nav>
<LinkContainer to="/shop">
<NavItem eventKey={1}>Shop</NavItem>
</LinkContainer>
</Nav>
<Nav pullRight>
{this.state.login ?
<LinkContainer to="/addtocart">
<NavItem eventKey={1}>Add To cart</NavItem>
</LinkContainer>
:
<LinkContainer to="/login">
<NavItem eventKey={1}>Login</NavItem>
</LinkContainer>
}
<LinkContainer to="/signip">
<NavItem eventKey={1}>Sigup</NavItem>
</LinkContainer>
</Nav>
</Navbar.Collapse>
</Navbar>
)
}
}
export default Header;
and in login
import React from 'react';
import { FormGroup, ControlLabel, FormControl } from 'react-bootstrap';
import { UserData } from '../../PostData';
import { ClipLoader } from 'react-spinners';
import { Redirect } from 'react-router-dom'
class Login extends React.Component {
state = {
username: undefined,
email: undefined,
loading: false,
redirect: false
}
onSubmit = (e) => {
e.preventDefault();
this.setState({ loading: true });
if (this.state.email && this.state.email !== '') {
try {
UserData(this.state.username, this.state.email).then(result => {
this.setState({ loading: false })
if (result.length) {
sessionStorage.setItem("login", JSON.stringify(result));
this.setState({ redirect: true })
} else {
alert("please enter the write email and username");
}
})
}
catch (e) {
console.log(e)
}
} else {
console.log("black")
}
}
onChangeHandle = (e) => {
this.setState({ [e.target.name]: e.target.value })
}
render() {
if (this.state.redirect) {
return <Redirect to="/" />
}
return (
<div>
<div style={{ position: 'absolute', top: '20%', left: '50%', zIndex: '111111' }}>
<ClipLoader
sizeUnit={"px"}
size={150}
color={'#123abc'}
loading={this.state.loading}
/>
</div>
<div className="container">
<form onSubmit={this.onSubmit}>
<FormGroup
controlId="formBasicText"
// validationState={this.getValidationState()}
>
<ControlLabel>username</ControlLabel>
<FormControl
type="text"
placeholder="Enter username"
name="username"
onChange={this.onChangeHandle}
/>
<FormControl.Feedback />
<ControlLabel>Email</ControlLabel>
<FormControl
type="text"
name="email"
placeholder="Enter email"
onChange={this.onChangeHandle}
/>
<FormControl.Feedback />
<input type="submit" className="btn btn-primary" value="login"></input>
</FormGroup>
</form>
</div>
</div>
)
}
}
export default Login;
`
import React from 'react';
import { FormGroup, ControlLabel, FormControl } from 'react-bootstrap';
import { UserData } from '../../PostData';
import { ClipLoader } from 'react-spinners';
import { Redirect } from 'react-router-dom'
class Login extends React.Component {
state = {
username: undefined,
email: undefined,
loading: false,
redirect: false
}
onSubmit = (e) => {
e.preventDefault();
this.setState({ loading: true });
if (this.state.email && this.state.email !== '') {
try {
UserData(this.state.username, this.state.email).then(result => {
this.setState({ loading: false })
if (result.length) {
sessionStorage.setItem("login", JSON.stringify(result));
this.setState({ redirect: true })
} else {
alert("please enter the write email and username");
}
})
}
catch (e) {
console.log(e)
}
} else {
console.log("black")
}
}
onChangeHandle = (e) => {
this.setState({ [e.target.name]: e.target.value })
}
render() {
if (this.state.redirect) {
return <Redirect to="/" />
}
return (
<div>
<div style={{ position: 'absolute', top: '20%', left: '50%', zIndex: '111111' }}>
<ClipLoader
sizeUnit={"px"}
size={150}
color={'#123abc'}
loading={this.state.loading}
/>
</div>
<div className="container">
<form onSubmit={this.onSubmit}>
<FormGroup
controlId="formBasicText"
// validationState={this.getValidationState()}
>
<ControlLabel>username</ControlLabel>
<FormControl
type="text"
placeholder="Enter username"
name="username"
onChange={this.onChangeHandle}
/>
<FormControl.Feedback />
<ControlLabel>Email</ControlLabel>
<FormControl
type="text"
name="email"
placeholder="Enter email"
onChange={this.onChangeHandle}
/>
<FormControl.Feedback />
<input type="submit" className="btn btn-primary" value="login"></input>
</FormGroup>
</form>
</div>
</div>
)
}
}
export default Login;
`

how to redirect to specifc route from a reducer/componet after event click in react redux

This is my Component Login Screen, i want to check isAuthenticated is true here using props i'm able to see my flag is set to true here but i want to redirect after hit the login how can i do this?
export class LoginPage extends Component {
onLogin = () => {
var credentials = {
username: this.userName,
password: this.password
}
this.props.checkLogin(credentials);
}
render() {
return (
<form>
<div className="imgcontainer">
<img src={img} alt="Avatar" className="avatar" />
</div>
<div className="container">
<label><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="uname" onChange={(e) => this.userName = e.target.value} />
<label><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="psw" onChange={(e) => this.password = e.target.value} />
<button type="submit" onClick={(event) => { event.preventDefault(); this.onLogin(); this.isUserClicked = true; }}>Login</button>
<label style={{ color: 'red' }}>{this.isUserClicked && !this.props.state.reducer.isAuthenticated.isUserAuthenticated && <span>Username and password not matched..!</span>}</label>
</div>
</form>
);
}
}
This is my map state props function
export const mapStateToProps = (state) => {
return {
state: state
}
}
my dispatcher dunction to call reducer event and i'm checking the username and password there and i'm returing the state.isAuthenticated is true
export const mapDispatchToProps = (dispatch) => {
return {
checkLogin: (credentials) => dispatch({ type: 'CHECK_LOGIN', credentials })
}
};
LoginPage = connect(mapStateToProps, mapDispatchToProps)(LoginPage);
export default LoginPage;
And this is my Routing File
import React, { Component } from 'react';
import logo from './logo.svg';
import { Route, Link } from 'react-router-dom'
import GamesPage from './GamesPage';
import LoginPage from './LoginPage';
import './App.css';
import AddBlogPost from './AddBlogPost';
import MembersList from './MembersList';
import Comments from './Comments';
export class HomePage extends Component {
constructor(state)
{
super();
console.log("state is:",state);
}
render() {
return (
<div className="col-md-12" style={{ paddingLeft: 'unSet' }}>
<div className="col-md-3" style={{ paddingLeft: 'unSet' }}>
<div className="sidebar-nav">
<div className="well" style={{ width: '300px', padding: '8px 0px' }}>
<ul className="nav nav-list" style={{ height: '950px' }}>
<li className="nav-header" style={{ marginLeft: '45px', marginBottom: '50px' }}><img src={logo} className="App-logo" alt="logo" /></li>
<li className="active"><i className="fa fa-home"></i> Dashboard</li>
<li><i className="fa fa-edit"></i> <Link to="/AddBlog">Add Blog Post</Link></li>
<li><i className="fa fa-sign-in"></i> <Link to="/login">Login</Link></li>
<li><i className="fa fa-user"></i> <Link to="/members">Members</Link></li>
<li><i className="fa fa-comment"></i><Link to="/comments">Comments</Link> </li>
</ul>
</div>
</div>
</div>
<div className="col-md-6">
<Route exact path="/games" component={GamesPage} />
<Route path="/login" component={LoginPage} />
<Route path="/AddBlog" component={AddBlogPost} />
<Route path="/members" component={MembersList} />
<Route path="/comments" component={Comments} />
</div>
</div>
);
}
}
export default HomePage;

Redux Component not receiving children

So I am developing a React + Redux app.
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={AppContainer}>
<IndexRoute component={Home}/>
<Route path="login" component={LoginContainer}/>
<Route path="protected" component={Protected} onEnter={checkUserIsLoggedIn}/>
<Route path="*" component={NotFound}/>
</Route>
</Router>
</Provider>
AppContainer.jsx
const App = function(props){
var isAuthenticated = props.login.isAuthenticated;
return <Grid>
<h1>Welcome to app!</h1>
<nav>
{isAuthenticated ? <Link to="/logout">Logout</Link> : <Link to="/login">Login</Link> }|
<Link to="/">Home</Link> |
<Link to="/protected">Protected</Link> |
</nav>
<div>
{props.children}
</div>
</Grid>
};
const mapStateToProps = (state) => {
var loginState = state.login;
return {
login: {
apiToken: loginState.apiToken,
isAuthenticated: loginState.isAuthenticated
}
}
};
const AppContainer = connect(
mapStateToProps
)(App);
Home, NotFound and Protected are stateless components, i.e.
const Home = (props) => <h2>Home</h2>;
While Login.jsx
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import { Row, Col, Panel, Button, Alert, Form, FormGroup, FormControl, InputGroup, Glyphicon } from 'react-bootstrap'
class Login extends Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
this.activateSignup = this.activateSignup.bind(this);
}
onSubmit (e) {
e.preventDefault();
let username = ReactDOM.findDOMNode(this.refs.username).value;
let password = ReactDOM.findDOMNode(this.refs.password).value;
this.props.onLoginSubmit(username, password);
}
activateSignup (e){
alert('does nothing for now');
}
render () {
return (
<Col style={{marginTop: "50px"}} md={6} mdOffset={3} sm={8} smOffset={2}>
<Panel header="Sign In" bsStyle="info">
<Col sm={12}>
{this.props.apiToken && <Alert bsStyle="success">
<strong>Welcome!</strong>
</Alert>}
</Col>
<Form horizontal onSubmit={this.onSubmit}>
<InputGroup style={{marginBottom: "25px", paddingTop: "15px"}}>
<InputGroup.Addon><Glyphicon glyph="user"/></InputGroup.Addon>
<FormControl ref="username"
type="text"
placeholder="username or email"
required/>
</InputGroup>
<InputGroup style={{marginBottom: "25px"}}>
<InputGroup.Addon><Glyphicon glyph="lock"/></InputGroup.Addon>
<FormControl ref="password"
type="password"
placeholder="password"
required/>
</InputGroup>
<InputGroup>
<FormGroup>
<Col sm={12}>
<Button id="btn-login" bsStyle="success" type="submit" disabled={this.props.isFetching}>Login</Button>
</Col>
</FormGroup>
<FormGroup>
<Col md={12}>
<div style={{borderTop: "1px solid#888", paddingTop: "15px", "fontSize": "85%"}}>
Don't have an account!{' '}
<a href="#" onClick={this.activateSignup}>
Sign Up Here
</a>
</div>
</Col>
</FormGroup>
</InputGroup>
</Form>
</Panel>
</Col>
)
}
}
Login.propTypes = {
isFetching: PropTypes.bool,
error: PropTypes.string,
apiToken: PropTypes.string
};
export default Login;
LoginContainer.jsx
import { connect } from 'react-redux'
import { fetchLogin } from '../actions/login-actions'
import Login from '../views/Login.jsx'
const mapStateToProps = (state) => {
return state.login
};
const mapDispatchToProps = (dispatch) => {
return {
onLoginSubmit: (username, password) => {
dispatch(fetchLogin(username, password))
}
}
};
const LoginContainer = connect(
mapStateToProps,
mapDispatchToProps
)(Login);
export default LoginContainer
So, I don't know why this is happening but I think it is because of the misuse between Redux and ReactRouter.
My /login renders perfectly. However, whenever I go to another route, the children components are not rendered because App.props.children is always null when using AppContainer. However, if I change the route path "/" component to use App instead of AppContainer, children are rendered successfully.
What am I doing wrong?

Resources