I have a simple LoginForm and I try to map the states from redux to react. Here is my code from LoginForm.js:
export class LoginForm extends React.Component {
render() {
console.log("**** FORM print store");
console.log(store.getState());
console.log("**** FORM print props");
console.log(this.props);
if(this.props.loginObj.loginState=='true') { // error here
console.log('yes');
}
return (
<div><div/>);
);
}
}
const mapStateToProps = (state) => ({
loginObj : state.loginObj
});
const mapDispatchToProps = (dispatch) => ({
doLogin,
changeText,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(LoginForm);
The reducer contains the property loginObj, if I print the store I see:
loginObj:
Object { loginState="false", user="", password=""}
Reducer:
const reducers = combineReducers({
loginObj: loginReducer
});
...
However when I try to access the props, it seems that this.props is empty.
this.props.loginObj.loginState - this.props is null
UPDATE:
LoginForm:
import {bindActionCreators} from 'redux'; //must include
function mapStateToProps(state){
return {
loginObj : state.loginObj
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({doLogin,changeText},dispatch)
};
First of all your correct your mapStateToProps method . It doesn't have return value. It shows clearly from the syntax of the method.
Second why are you using this.props.loginObj.loginState ? This abstraction level is wrong. As you have mentioned loginObj as props, then why you should abstract loginState property there? It'll not work.
In redux, you should limit props to same object in the state. For example, if you want todos from state, then mention todos as props. If you want some property of todos, then pass down it to props(for this code) or via children props.
I don't know about your store but, the component should look like:
export class LoginForm extends React.Component {
render() {
console.log("**** FORM print store");
console.log(store.getState());
console.log("**** FORM print props");
console.log(this.props);
if (this.props.loginObj.loginState == 'true') { // error here
console.log('yes');
}
// for this issue you could view at this point -
// store.getState().loginObj.propertyyouwant only if you have
// imported main store in this current file
return (
<div>
</div>
);
}
}
const mapStateToProps = (state) => {
return { loginObj: state.loginObj}
};
const mapDispatchToProps = (dispatch) =>
{
return {
doLogin,
changeText,}
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(LoginForm);
Related
I have a redux State HOC to manage the connection
I Have a problem when I add a new post to the store
import React, { useEffect } from "react";
import { connect } from "react-redux";
export default function withState(WrappedComponent) {
function mapStateToProps(reduxState) {
let state = {};
for(let t of Object.entries(reduxState)) {
state = {...state, ...t[1]}
}
return {
...state,
};
}
return connect(
mapStateToProps,
null
)(function (props) {
useEffect(() => {}, [props.posts, props.comments]) /*tried this but didn't work*/
return (
<React.Fragment>
<WrappedComponent {...props} />
</React.Fragment>
);
});
}
I am trying to make the program render the response from my back-end without me reloading the page manually
I tried using the useEffect
and I saw through the dev tools that the state change correctly
my reducer
import { GET_ALL_POSTS, CREATE_NEW_POST } from "../actions"
const initialState = {
posts: []
}
export default function postReducer(state = initialState, action) {
let newState = {...state}
switch(action.type){
case GET_ALL_POSTS:
return {
...newState,
posts: [...action.posts],
}
case CREATE_NEW_POST:
const posts = [...newState.posts, action.post]
return {
...newState,
posts
}
default:
return {
...newState,
}
}
}
I also read that react changes doesn't respond to shallow copies so I changed the whole array in the post reduces when I add a new post
Your withState HOC is very strange. I'm not sure why you don't just use connect directly (or use hooks). But try this:
export function withState(WrappedComponent) {
return connect(
(state) => ({
posts: state.postsReducer.posts,
comments: state.commentsReducer.comments
}),
null
)(WrappedComponent);
}
Not able to access the redux store current state in a Class component.
It shows up console error
Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
When I tried to implement the same using a function component with useSelector and useDispatch, everything works as expected. What has gone wrong over here?
reducer.js
let initialState={
count:0
}
const reducer=(state=initialState,action)=>{
switch(action.type){
case ADD_INCREMENT:
return {
...state,
count:state.count+1
};
default: return state;
}
}
export default reducer;
action.js
const Increment=()=>{
return {
type:ADD_INCREMENT
}
}
store.js
import reducer from './reducer';
const store=createStore(reducer);
export default store;
Class Component
import { connect } from 'react-redux';
const mapStateToProps=state=>{
return {
count:state.count
}
}
const mapDispatchToProps=(dispatch)=>{
return {
count:()=>dispatch(action.Increment())
}
}
class Orders extends Component {
render() {
return (
<div>
<h1>Count: {this.props.count} </h1>
</div>
);
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Orders);
In App.js the entire container is wrapped with Provider and store is passed as props
Issue
You've named your state and your action both count, the latter is the one injected as a prop.
const mapStateToProps = state => {
return {
count: state.count // <-- name conflict
}
}
const mapDispatchToProps = (dispatch) => {
return {
count: () => dispatch(action.Increment()) // <-- name conflict
}
}
Solution
Provide different names, count for the state, maybe increment for the action.
const mapStateToProps = state => ({
count: state.count,
});
const mapDispatchToProps = (dispatch) => ({
increment: () => dispatch(action.Increment())
})
In my code below, I have a delete button that should be deleting the data if clicked. However, when I click on it, I am seeing through console.log that it is returning undefined instead of the id number. Can't seem to figure out why. Any help will be greatly appreciated. Thank you.
//Actions File
export const GET_ITEMS = 'GET ITEMS';
export const FETCH_ITEMS_SUCCESS = 'FETCH ITEMS SUCCESS';
export const FETCH_ITEMS_ERROR = 'FETCH ITEMS ERROR';
export const DELETE_ITEM = 'DELETE_ITEM';
export const getItems = () => ({
type: GET_ITEMS
});
export const deleteItem = (itemId) => ({
type : DELETE_ITEM,
payload: itemId
});
//App.js
class App extends Component {
componentDidMount() {
this.props.getItems()
}
static propTypes = {
getItems: PropTypes.func.isRequired,
deleteItem: PropTypes.func.isRequired
}
handleDelete = (id) =>{
this.props.deleteItem(id)
console.log(this.props.deleteItem(id));
}
render() {
const { itemsList} = this.props.items
return (
<div className="container app-wrapper">
<header>
{itemsList.map(item => (<h1 key={item.id}>{item.title} <button onClick={this.handleDelete.bind(this, item.id)}>delete</button></h1>))}
</header>
</div>
);
}
}
const mapStateToProps = state => ({
items: state.items
});
export default connect(mapStateToProps, {getItems, deleteItem})(App);
The dispatched action should return undefined, because it does not return anything. You are misunderstanding how data flows in the Redux/reducer pattern.
Here's the basic flow of a Redux update:
Action is dispatched.
All reducers receive the action object.
All reducers return their new or previous state depending on that action's contents.
connect sees that the Redux state has changed, and triggers a re-render of the children components.
You may now use the updated data from your Redux store through props (mapped in mapStateToProps).
You cannot call an action and receive the updated state as the return value. It breaks the fundamental pattern of how data flows/updates in Redux.
You are referencing your delete action incorrectly in connect. deleteItem expects an id param passed into it.
Try this,
const mapStateToProps = state => ({
items: state.items
});
const mapDispatchToProps = (dispatch) =>
{
return {
deleteItem: (id) => dispatch(actions.deleteItem(id)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
I need to change the "global" state of Redux (I believe it's called storage). This is my code:
reducer
export const user = (state = {}, action) => {
console.log(4);
console.log(action.type)
console.log(action.payload)
switch (action.type) {
case C.SET_USER:
console.log(action.payload);
return action.payload;
case C.CLEAR_USER:
return action.payload;
default:
return state;
}
};
Action:
export const setUser = (user = {}) => {
console.log(user);
return {
type: C.SET_USER,
payload: user,
}
};
Calling the action:
const user = {test:true};
setUser(this.state.user);
But if I run this code, it fails and doesn't call the reducer. It calls the action, but not the reducer. What am I missing?
My current app.js code:
export default class App extends Component {
constructor(p) {
super(p);
this.state = {user: null};
}
setUser = () => {
const {uid} = firebase.auth().currentUser;
firebase.database().ref('Users').child(uid).on('value', r => {
const user = r.val();
this.setState({user: user});
console.log(this.state.user);
setUser(this.state.user);
});
};
componentWillMount() {
if (firebase.auth().currentUser) {
this.setUser();
}
firebase.auth().onAuthStateChanged(async () => {
console.log('authChanged');
if (!firebase.auth().currentUser) {
return null;
}
this.setUser();
});
}
render() {
return (
<div className="App">
<Nav/>
</div>
);
}
}
setUser have to be dispatched and not simply called:
store.dispatch(setUser(user));
But that's not really the react way, you'd better use mapDispatchToProps in your connect function to dispatch actions directly from component props. Something along the lines of:
import { setUser } from 'store/user';
// ...
class UserComponent extends React.Component {
// ...
someMethod() {
this.props.setUser(user);
}
}
export default connect(
null,
({setUser: setUser})
)(UserComponent);
This allows your React component to be linked to your Redux store in an optimized and bug-free way. That's also the way most developer use, so you're likely to find a lot of docs on this.
Example: Your connected Component where you want to use your setUser action with redux
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setUser} from '../../actions';
class YourComponent extends Component {
render(){
// now your redux action is passed to component as prop
// and you can use it like
this.props.setUser(some-user);
return()
}
}
export default connect(null, {setUser})(YourComponent);
first of all you have to dispatch action to change the state , second you have to connect your component to the store
to connect your component to the store
...
import { connect } from 'react-redux';
class MyComponent extends React.Component {
...
}
export default connect((store) => ({...}))
when you connect your component to the store you will have access to dispatch function in the props
to dispatch action do this :
this.props.dispatch(setUser());
I believe it's called storage
BTW it called store
Im new to React and Redux and still kinda confused a little bit.
My goal is to render a bunch of json datas in the HTML by using GET request. I'm using react and redux to manage the state of the objects, but I believe my problem is that the data is not even there
so basically whenever someone request a URL /courses , he/she will see bunch of data in json.
I get the error in the component
TypeError: Cannot read property 'map' of undefined
Here's the code
Action
export function getCourses() {
return (dispatch) => {
return fetch('/courses', {
method: 'get',
headers: { 'Content-Type', 'application/json' },
}).then((response) => {
if (response.ok) {
return response.json().then((json) => {
dispatch({
type: 'GET_COURSES',
courses: json.courses
});
})
}
});
}
}
Reducer
export default function course(state={}, action) {
switch (action.type) {
case 'GET_COURSES':
return Object.assign({}, state, {
courses: action.courses
})
default:
return state;
}
}
Component
import React from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
class Course extends React.Component {
allCourses() {
return this.props.courses.map((course) => {
return(
<li>{ course.name }</li>
);
});
}
render() {
return (
<div>
<ul>
{ this.allCourses() }
</ul>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
courses: state.courses
}
}
export default connect(mapStateToProps)(Course);
Index reducer, where i combine everything
import { combineReducers } from 'redux';
import course from './course';
export default combineReducers({
course,
});
Configure Store , where i store the intial state and the reducer
import { applyMiddleware, compose, createStore } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk),
typeof window == 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f
)
);
return store;
}
I believe why the data is not there is because i didn't call the action? any help would be appreciated.
mapStateToProps takes the root state as an argument (your index reducer, which is also the root reducer), not your course reducer. As far as I can tell this is the structure of your store:
-index <- This is the root reducer
-course
So to get the courses from that state, in your component:
// state is the state of the root reducer
const mapStateToProps = (state) => {
return {
courses: state.course.courses
}
}
Also, you might consider initialising the state of the course reducer with an empty array of courses, so if you have to render the component before the action is fired, you won't get the error.
const initialState = {
courses: []
};
export default function course(state= initialState, action) {
...
}
Finally, you're not firing the action at all, so you will never actually get the courses, I assume you want them to be retrieved once the Course component is loaded, for that you can use the componentDidMount event in your component.
First of all, you need to map the action to a property of the component
// Make sure you import the action
import { getCourses } from './pathToAction';
...
const mapDispatchToProps = (dispatch) => {
return {
onGetCourses: () => dispatch(getCourses())
};
}
// Connect also with the dispatcher
export default connect(masStateToProps, mapDispatchToProps)(Course);
Now call the onGetCourses property when the component mounts
class Course extends React.Component {
componentDidMount() {
this.props.onGetCourses();
}
...
}
its because props sometime can be undefined so you have to write a condtion like this
allCourses() {
if(this.props.courses){
return this.props.courses.map((course) => {
return(
<li>{ course.name }</li>
);
});
}
else {
return [];
}