Im trying to log out a user as well as displaying a register/login div or profile section, depending if a user is logged in or not, Im trying to do this on a component that gets render on my main app.
Using ReduxDevTools I can see that the auth state changes when I log in a user or not, but when I try to render the LeftContainer setting auth: state.auth I get auth is undefined,
error:
Cannot read property 'isAuthenticated' of undefined
Warning: Failed prop type: The prop auth is marked as required in LeftContainer, but its value is undefined.
LeftContainerProfile/index.js
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router'
import { connect } from 'react-redux';
import { logout } from '../../containers/SignIn/authActions';
import styled from 'styled-components';
import Logo from '../Logo/Logo';
import SocialLinks from '../SocialLinks/social_links';
import ProfileContainer from './profile';
import LinksContainer from './about_links';
const Wrapper = styled.div`
position: absolute;
top: 250px;
width: 20%;
padding-left: 4%;
padding-right: 4%;
// height: 100vh;
`;
const Button = styled(Link)`
width: 100px;
height: 50px;
background-color: red;
color: white;
margin: 5px;
`;
class LeftContainer extends React.Component { // eslint-disable-line react/prefer-stateless-function
logout(e) {
e.preventDefault();
this.props.logout();
}
render() {
const { isAuthenticated } = this.props.auth;
const userLinks = (
<div>
<ProfileContainer />
<Button onClick={this.logout.bind(this)}> LOGOUT </Button>
</div>
);
const guestLinks = (
<div>
<Button to='/login'>LOGIN</Button>
<Button to='/sign-up'>REGISTER</Button>
</div>
);
return (
<Wrapper>
{ isAuthenticated ? userLinks : guestLinks }
<LinksContainer />
<SocialLinks />
</Wrapper>
);
}
}
LeftContainer.propTypes = {
auth: React.PropTypes.object.isRequired,
logout: React.PropTypes.func.isRequired
}
function mapStateToProps(state) {
return {
auth: state.auth
};
}
export default connect(mapStateToProps, { logout })(LeftContainer);
Im trying to get the state of auth but i get either undefined or object
here is a picture from reduxdevtools
https://gyazo.com/b189a75e1123cb198412adf93bdd3497
edit 3: When I do this
function mapStateToProps(state) {
console.log(state);
return {
isAuthenticated: state.auth
};
}
I get this
https://gyazo.com/432247229816e4a68a50133834b551c9
maybe this will help people in helping find the solution to my problem
edit 4: I found a solution but Im not sure if this was the correct way of going about this, can someone maybe explain why state.auth was not giving my desire result, is it something in my reducer or something else? here is my solution
class LeftContainer extends React.Component { // eslint-disable-line react/prefer-stateless-function
logout(e) {
e.preventDefault();
this.props.logout();
}
render() {
const { isAuthenticated } = this.props;
const userLinks = (
<div>
<ProfileContainer />
<Button onClick={this.logout.bind(this)}> LOGOUT </Button>
</div>
);
const guestLinks = (
<div>
<Button to='/login'>LOGIN</Button>
<Button to='/sign-up'>REGISTER</Button>
</div>
);
return (
<Wrapper>
{ isAuthenticated ? userLinks : guestLinks }
<div>
{/*<Button to='/login'>LOGIN</Button>
<Button to='/sign-up'>REGISTER</Button>*/}
</div>
<LinksContainer />
<SocialLinks />
</Wrapper>
);
}
}
// LeftContainer.propTypes = {
// auth: React.PropTypes.object.isRequired,
// logout: React.PropTypes.func.isRequired
// }
// LeftContainer.propTypes = {
// isAuthenticated: React.PropTypes.bool.isRequired,
// logout: React.PropTypes.func.isRequired
// }
// LeftContainer.defaultProps = {
// isAuthenticated: true,
// }
function mapStateToProps(state) {
console.log(state._root.entries[3][1].isAuthenticated);
return {
isAuthenticated: state._root.entries[3][1].isAuthenticated
};
}
export default connect(mapStateToProps, { logout })(LeftContainer);
or a much simpler approach (than the one I've listed in the comment):
I treated the isAuthenticated directly from the mapeStateToProps function and made it a bool value for more readiblity. This is less error prone because you don't pass a nested object on props. Also as a fallback I've added a defaultProp value.
Hope this does the trick.
class LeftContainer extends React.Component { // eslint-disable-line react/prefer-stateless-function
logout(e) {
e.preventDefault();
this.props.logout();
}
render() {
const { isAuthenticated } = this.props;
const userLinks = (
<div>
<ProfileContainer />
<Button onClick={this.logout.bind(this)}> LOGOUT </Button>
</div>
);
const guestLinks = (
<div>
<Button to='/login'>LOGIN</Button>
<Button to='/sign-up'>REGISTER</Button>
</div>
);
return (
<Wrapper>
{ isAuthenticated ? userLinks : guestLinks }
<LinksContainer />
<SocialLinks />
</Wrapper>
);
}
}
LeftContainer.propTypes = {
isAuthenticated: React.PropTypes.bool.isRequired,
logout: React.PropTypes.func.isRequired
}
LeftContainer.defaultProps = {
isAuthenticated: false,
}
function mapStateToProps(state) {
return {
isAuthenticated: state.auth && state.auth.isAuthenticated
};
}
export default connect(mapStateToProps, { logout })(LeftContainer);
Related
I am trying to create a simple form app, where there will be a textarea input and a submit button. Where, if I type something in the textarea and then click submit, the text that I just typed will show under the button inside a tag. When im doing this without Redux, it works fine, even after when I use Redux partly meaning when I manage only one state (input field state) using Redux it works great. But when i make two reducers, and two dispatches then problem happens. Here are my codes.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Provider from 'react-redux/es/components/Provider';
import {
createStore,
applyMiddleware,
combineReducers,
} from 'redux';
import { getInput, getOutput } from './reducer';
import { createLogger } from 'redux-logger';
import App from './App';
import reportWebVitals from './reportWebVitals';
const rootReducer = combineReducers({
getInput,
getOutput,
});
const logger = createLogger();
const store = createStore(
rootReducer,
applyMiddleware(logger)
);
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
app.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
changeInput,
postOutput,
} from './action';
import {
Form,
Button,
Container,
} from 'react-bootstrap';
const mapStateToProps = (state) => {
return {
input: state.getInput.input,
output: state.getOutput.output,
};
};
const mapDispatchToProps = (dispatch) => {
return {
handleInput: (event) =>
dispatch(changeInput(event.target.value)),
handleClick: (props) =>
dispatch(postOutput(props.output)),
};
};
class App extends Component {
// constructor() {
// super();
// this.state = {
// output: '',
// };
// }
// handleInput = (event) => {
// this.setState({ input: event.target.value });
// };
// handleClick = () => {
// this.setState({
// output: this.props.input,
// });
// };
render() {
return (
<div>
<Container>
{' '}
<Form>
<Form.Group controlId='exampleForm.ControlTextarea1'>
<div>
<div
style={{
display: 'flex',
justifyContent: 'center',
marginTop: '20px',
marginBottom: '10px',
}}>
<Form.Control
as='textarea'
rows={5}
placeholder='enter something here'
onChange={this.props.handleInput}
style={{ width: '500px' }}
/>
</div>
<div
style={{
display: 'flex',
justifyContent: 'center',
}}>
<Button
variant='primary'
onClick={this.props.handleClick}>
Submit
</Button>
</div>
</div>
</Form.Group>
</Form>
</Container>
<div
style={{
display: 'flex',
justifyContent: 'center',
}}>
<h1 value={this.props.input}>
{this.props.output}
</h1>
</div>
</div>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
action.js
import {
CHANGE_INPUT_FIELD,
POST_OUTPUT,
} from './constant';
export const changeInput = (text) => ({
type: CHANGE_INPUT_FIELD,
payload: text,
});
export const postOutput = (text) => ({
type: POST_OUTPUT,
payload: text,
});
reducer.js
import {
CHANGE_INPUT_FIELD,
POST_OUTPUT,
} from './constant';
const initialStateInput = {
input: '',
};
const initialStateOutput = {
output: '',
};
export const getInput = (
state = initialStateInput,
action = {}
) => {
switch (action.type) {
case CHANGE_INPUT_FIELD:
return Object.assign({}, state, {
input: action.payload,
});
default:
return state;
}
};
export const getOutput = (
state = initialStateOutput,
action = {}
) => {
switch (action.type) {
case POST_OUTPUT:
return Object.assign({}, state, {
output: action.payload,
});
default:
return state;
}
};
constant.js
export const CHANGE_INPUT_FIELD =
'CHANGE_INPUT_FIELD';
export const POST_OUTPUT = 'POST_OUTPUT';
changeInput action must be handled inside the component there is no reason to dispatch an action and handle it with reducer because reducer is for managing shared states.
Can you specify what is the "problem"?
The problem is not with actions, you cannot see the value because the value is set to undefined
In App.js you have to pass the correct value
onClick={this.props.handleClick}> must change as onClick={this.props.handleClick(this.props)}> otherwise props will be equal to event object in the line handleClick: (props) => dispatch(postOutput(props.output))
Still you won't see the value in UI because the output value is set to '' because you are not setting the input value to the output value in reducer.
My suggestion there must be another action that fires when submit button is clicked and sets the current input value to the input, then fire getOutput
So, as the title suggests, Cards component is receiving props from UserPosts, as well as it's connected to the store to dispatch an action. But it looks like this is not working at all. Connecting a component is not working for me. Maybe I am missing something? Can someone show me the correct way to do it. I'm trying to delete a post on clicking on the delete button.
Here is the code.
UserPosts
import React, { Component } from "react"
import { getUserPosts, getCurrentUser } from "../actions/userActions"
import { connect } from "react-redux"
import Cards from "./Cards"
class UserFeed extends Component {
componentDidMount() {
const authToken = localStorage.getItem("authToken")
if (authToken) {
this.props.dispatch(getCurrentUser(authToken))
if (this.props && this.props.userId) {
this.props.dispatch(getUserPosts(this.props.userId))
} else {
return null
}
}
}
render() {
const { isFetchingUserPosts, userPosts } = this.props
return isFetchingUserPosts ? (
<p>Fetching....</p>
) : (
<div>
{userPosts &&
userPosts.map(post => {
return <Cards key={post._id} post={post} />
})}
</div>
)
}
}
const mapStateToPros = state => {
return {
isFetchingUserPosts: state.userPosts.isFetchingUserPosts,
userPosts: state.userPosts.userPosts,
userId: state.auth.user._id
}
}
export default connect(mapStateToPros)(UserFeed)
Cards
import React, { Component } from "react"
import { connect } from "react-redux"
import { deletePost } from "../actions/userActions"
class Cards extends Component {
handleDelete = postId => {
this.props.dispatch(deletePost(postId))
}
render() {
const { _id, title, description } = this.props.post
return (
<div className="card">
<div className="card-content">
<div className="media">
<div className="media-left">
<figure className="image is-48x48">
<img
src="https://bulma.io/images/placeholders/96x96.png"
alt="Placeholder image"
/>
</figure>
</div>
<div className="media-content" style={{ border: "1px grey" }}>
<p className="title is-5">{title}</p>
<p className="content">{description}</p>
<button className="button is-success">Edit</button>
<button
onClick={this.handleDelete(_id)}
className="button is-success"
>
Delete
</button>
</div>
</div>
</div>
</div>
)
}
}
const mapStateToProps = () => {
return {
nothing: "nothing"
}
}
export default connect(mapStateToProps)(Cards)
deletePost
export const deletePost = (id) => {
return async dispatch => {
dispatch({ type: "DELETING_POST_START" })
try {
const deletedPost = await axios.delete(`http://localhost:3000/api/v1/posts/${id}/delete`)
dispatch({
type: "DELETING_POST_SUCCESS",
data: deletedPost
})
} catch(error) {
dispatch({
type: "DELETING_POST_FAILURE",
data: { error: "Something went wrong" }
})
}
}
}
Should be something like this:
const mapDispatchToProps(dispatch) {
return bindActionCreators({ deletePost }, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Cards)
And then call it as a prop:
onClick={this.props.deletePost(_id)}
In my project, I am persisting state of option buttons in redux. There are different buttons group and I am handling their click action in single function handleClick. But it seems like it’s not working. Should I create a different handler for each button group? Can anyone suggest the best solution?
code :
import React, { Component } from "react";
import { Button } from "semantic-ui-react";
import { withRouter } from "react-router";
import Answers from "../Answers/Answers";
import { handleClick } from "../../actions/handleClickAction"
import { connect } from 'react-redux'
class Section extends Component {
handleClick = event => {
this.props.handleClick(event);
};
render() {
console.log(this.state);
let styles = {
width: '50%',
margin: '0 auto',
marginBottom: '15px'
}
const { history } = this.props;
const { que1, que2, que3 } = this.state;
return (
<>
<p>1. I was stressed with my nerves on edge.</p>
<Button.Group widths="5" onClick={this.handleClick} style={styles}>
<Answers selected={this.state.que1} style={{ backgroundColor: 'red' }} />
</Button.Group>
{` `}
<p>2. I lost hope and wanted to give up when something went wrong.</p>
<Button.Group widths="5" onClick={this.handleClick} style={styles}>
<Answers selected={this.state.que2} style={{ backgroundColor: 'red' }} />
</Button.Group>
{` `}
<p>3. I feel very satisfied with the way I look and act</p>
<Button.Group widths="5" onClick={this.handleClick} style={styles}>
<Answers selected={this.state.que3} style={{ backgroundColor: 'red' }} />
</Button.Group>
<p />
{` `}
<Button
disabled={!que1 || !que2 || !que3}
onClick={() => history.push("/section2", [this.state])}
>
NEXT
</Button>
</>
);
}
}
export default withRouter(connect(null, { handleClick })(Section));
main.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import store from "./store";
import { Provider } from 'react-redux'
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById("root"));
index.js
import { combineReducers } from "redux";
import selectOptionReducer from "./selectOptionReducer";
export default combineReducers({
selectOption: selectOptionReducer
})
selectOptionReducer.js
import { SELECT_OPTION } from "../actions/types"
const initialState = {
que1: "",
que2: "",
que3: "",
que4: "",
que5: ""
}
export default (state = initialState, action) => {
switch (action.type) {
case SELECT_OPTION:
return {
...state,
que1: action.payload,
que2: action.payload,
que3: action.payload,
que4: action.payload,
que5: action.payload
};
default:
return state;
}
}
store.js
import { createStore } from 'redux'
import selectOptionReducer from "./reducers/selectOptionReducer";
const store = createStore(selectOptionReducer);
export default store;
handleClickAction.js
import { SELECT_OPTION } from "./types"
export const handleClick = e => {
return {
type: SELECT_OPTION,
payload: e.target.attributes.getNamedItem("data-key").value
}
}
output :
From what I can see, that reducer would be setting the state for all questions to the same answer on every action.
You need a way to specify which question is being answered.
I would go with something like the following which creates a custom onClick handler for each question and passes the question id to the action creator to be included in the reducer payload. The reducer then uses that id to only update the question being answered.
(untested)
selectOptionReducer.js
export default (state = initialState, action) => {
switch (action.type) {
case SELECT_OPTION:
const { questionId, value } = action.payload;
return { ...state, [questionId]: value };
default:
return state;
}
}
handleClickAction.js
export const handleClick = ({ questionId, e }) => {
return {
type: SELECT_OPTION,
payload: { questionId, value: e.target.attributes.getNamedItem("data-key").value }
}
}
component
class Section extends Component {
handleClick = questionId => e => {
this.props.handleClick({ questionId, e });
};
...
<Button.Group widths="5" onClick={this.handleClick("que1")} style={styles}>
I have this class:
import React from 'react';
import {Link} from 'react-router';
import '../../styles/about-page.css';
import Item from './Item';
// Redux
import { connect } from 'react-redux';
import actions from '../../redux/actions';
import { bindActionCreators } from 'redux';
// Auth
import Auth from '../../modules/Auth';
import User from '../../constants';
class CommentForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit() {
// alert('A name was submitted: ' + this.state.value);
this.props.onFinish({
name: this.props.user.name,
userId: this.props.user.id,
comment: this.state.value
});
}
render() {
if (!this.props.user || !this.props.user.name) return <div/>;
return (<div className="item">
{this.props.user.name}:
<label style={{width: '60%', margin: '10px'}}>
<input style={{width: '100%'}} type="text" value={this.state.value} onChange={this.handleChange.bind(this)} />
</label>
<input style={{width: '16%', display: 'inline-block', margin: '10px'}} type="submit" value="Enviar" onClick={this.handleSubmit.bind(this)}/>
</div>
);
}
}
#connect((state) => state)
class ItemPage extends React.Component {
constructor(props) {
super(props);
this.state = {
user: null,
pret: null
};
}
componentWillMount() {
let self = this;
this.props.actions.getPret(this.props.routeParams.id).then(function(a) {
self.setState({
pret: self.props.pret
})
});
if (Auth.isUserAuthenticated()) {
User.getBearer(Auth, function(info) {
self.setState({user: info});
});
}
}
onFinish(comment) {
//changing the state in react
//need to add '6' in the array
//create a copy of this.state.a
//you can use ES6's destructuring or loadash's _.clone()
const currentStatePretCopy = Object.assign({}, this.state.pret, { b: this.state.pret.comments.concat([comment])})
console.log(1, currentStatePretCopy);
currentStatePretCopy.comments.push(comment);
this.props.actions.updatePret(currentStatePretCopy);
}
render() {
let self = this;
if (!this.state || !this.state.pret) return <div/>;
return (<div>
<section>
<Item full={true} user={this.state.user} item={this.state.pret} actions={this.state.actions}/>
</section>
<div>
<CommentForm user={this.state.user} pret={this.state.pret} onFinish={this.onFinish.bind(this)}/>
{/* TODO: ad here */}
{this.state.pret.comments && this.state.pret.comments.length ? this.state.pret.comments.map(function (comment, index) {
return (<div className="item" key={index}> by <Link to={'/user/' + comment.userId}> #{comment.name} </Link> : {comment.comment} </div>);
}) : null}
</div>
</div>
);
}
}
function mapStateToProps (state) {
return {
pret: state.prets[0]
};
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ItemPage);
When i want to update the object, since props should be immutable, the documentation suggest cloning the object and put it in the state.
I am modifying the state with the new value and sending it to Redux actions but the system complains:
Uncaught Error: A state mutation was detected between dispatches, in the path 'prets.0.comments.1'. This may cause incorrect behavior.
Since i copy the object I do not know how should I update the store via React+Redux
The comments array is still the original one. So you are mutating the original object with push.
Replace
currentStatePretCopy.comments.push(comment);
With
currentStatePretCopy.comments = currentStatePretCopy.comments.concat([comment])
And everything should work fine
I'm working to use react-modal in my React+Redux+ReactRouter4 App.
I have a MainLayout container and a Home container.
The modal will only be used when the home container is rendered so I have ReactModal's logic inside the Home Container. And I can easily open the modal from the Home Container like so:
<button onClick={this.openModal}>Open Modal</button>
The problem is the MainLayout container has a navigation that also needs the ability to open the modal, but obviously, this.openModal does not exist there... How can I allow the MainLayout Container to open the modal in the Home container?
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
modalIsOpen: false
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
openModal() {
this.setState({modalIsOpen: true});
}
closeModal() {
this.setState({modalIsOpen: false});
}
render() {
return (
<div>
....
<button onClick={this.openModal}>Open Modal</button>
<Modal
isOpen={this.state.modalIsOpen}
onAfterOpen={this.afterOpenModal}
onRequestClose={this.closeModal}
style={modalCustomStyles}
contentLabel="Example Modal"
>
<h2 ref={subtitle => this.subtitle = subtitle}>Hi</h2>
<button onClick={this.closeModal}>close</button>
<div>I am a modal</div>
</Modal>
</div>
)
};
};
App.jsx
const WithMainLayout = ({component: Component, ...more}) => {
return <Route {...more} render={props => {
return (
<MainLayout {...props}>
<Component {...props} />
</MainLayout>
);
}}/>;
};
....
<WithMainLayout exact path="/" component={Home} />
What I would do is just move the modalOpenState into redux rather than keeping it in local state. Your initial state would be like this.
export default {
modalIsOpen: false
};
Then write an action to toggle the modal state in the store.
export function toggleQuestionModal(isOpen) {
return { type: types.TOGGLE_QUESTION_MODAL, payload: isOpen };
}
Your presentational component for modal should be something like this.
import React, { Component, PropTypes } from 'react';
import Modal from 'react-modal';
const QuestionModal = ({ modalIsOpen, openModal, closeModal, afterOpenModal }) => {
const customStyles = {
overlay: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.75)'
},
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
height: '50%',
width: '80%',
transform: 'translate(-50%, -50%)'
}
};
return (
<div>
<button onClick={openModal}>Open Modal</button>
<Modal
isOpen={modalIsOpen}
onAfterOpen={afterOpenModal}
onRequestClose={closeModal}
style={customStyles}
contentLabel="Create A Question"
role="dialog"
>
<h2>Hello</h2>
<button onClick={closeModal}>close</button>
<div>I am a modal</div>
<form>
<input />
<button>tab navigation</button>
<button>stays</button>
<button>inside</button>
<button>the modal</button>
</form>
</Modal>
</div>
);
};
QuestionModal.propTypes = {
modalIsOpen: PropTypes.bool.isRequired,
openModal: PropTypes.func.isRequired,
closeModal: PropTypes.func.isRequired,
afterOpenModal: PropTypes.func.isRequired
};
export default QuestionModal;
Finally here's your container component for the modal.
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { toggleQuestionModal, toggleConfirmation } from '../actions/questionActions';
import QuestionModal from '../components/questionModal';
class QuestionPage extends Component {
constructor(props, context) {
super(props, context);
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
this.afterOpenModal = this.afterOpenModal.bind(this);
}
openModal() {
this.props.toggleQuestionModal(true);
}
afterOpenModal() {
// references are now sync'd and can be accessed.
// this.subtitle.style.color = '#f00';
}
closeModal() {
this.props.toggleConfirmation(true);
}
render() {
const { modalIsOpen } = this.props;
return (
<div>
<QuestionModal modalIsOpen={modalIsOpen} openModal={this.openModal} closeModal={this.closeModal}
afterOpenModal={this.afterOpenModal} />
</div>
);
}
}
QuestionPage.propTypes = {
modalIsOpen: PropTypes.bool.isRequired,
toggleQuestionModal: PropTypes.func.isRequired,
};
function mapStateToProps(state, ownProps) {
return {
modalIsOpen: state.question.modalIsOpen
};
}
function mapDispatchToProps(dispatch) {
return {
toggleQuestionModal: bindActionCreators(toggleQuestionModal, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(QuestionPage);
When you want to open the modal from any component merely invoke the toggleQuestionModal action with a true value. This will change the state and render the modal. Redux recommends to keep everything in the state. I do practice that. Don't keep things local. Keeping everything in state makes it easier for you to do a time travel debug using tools. You may find sample implementation here. Hope this helps. Happy Coding !