i have a parent component and one child component (custom function component). in my child component i have event handler that will change the state (redux) from the parent. everything work fine when i call the addProduct function the state is added i can see in my redux tools product state is changed. But Why after i added i cant access that state (console.log (product._id)) i get a error message a product is null and one thing make me confused is i can access that state in my JSX from parent Component ({JSON.stringify(product)}) but not after i added.
here is my code
import React, { Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {addProduct} from '../action/product';
const ChildComponent = ({onClick}) => {
return (
<button onClick={onClick}>Add</button>
)
}
const ParentComponent = ({product}) => {
const onNotFoundThenAddNew = () => {
addProduct ({name : 'new product'});
console.log(product._id);
}
return (
<Fragment>
{JSON.stringify(product)}
<ChildComponent onClick={onNotFoundThenAddNew}/>
</Fragment>
)
}
ParentComponent.propTypes = {
addProduct : PropTypes.func,
product : PropTypes.object
};
const mapStateToProps = (state) => ({
product : state.product.product
})
export default connect (mapStateToProps, {addProduct}) (ParentComponent)
Related
I am using react with redux and I have a todo-list.
I have a 'Todos' component which acts like a container and a 'Todoitem' component which holds every todo.
Everything works fine - reducers change the state and it is updating with new data, but the child component (aka 'Todoitem' component) won't re-render.
Todos.js:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import TodoItem from "./TodoItem";
class Todos extends Component {
render() {
return (
<div className="Todos">
<div className="todos_title"> {this.props.title} </div>
{this.props.todos.map(todo => {
console.log(todo); // this line prints updated data from state just fine!
return <TodoItem todo={todo} key={todo.id}></TodoItem>;
})}
</div>
);
}
}
// PropTypes
Todos.propTypes = {
todos: PropTypes.array.isRequired
};
const mapStateToProps = state => ({
todos: state.todosReducer.todos
});
export default connect(mapStateToProps)(Todos);
TodoItem.js:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { checkTodo } from "../actions/todosAction";
class TodoItem extends Component {
onChange = (e, id) => {
console.log(this.props.todo.completed.toString()); // this also prints fine the updated data
this.props.checkTodo(id); // dispatches an action to the reducer to toggle the todo.completed with the right todo.id
};
render() {
console.log('rendering'); // this is the problem - this line calls only in the first rendering, but not when state changes
let { id, title, completed } = this.props.todo;
return (
<div className={completed ? "TodoItemDone" : "TodoItem"}>
<p>
<input
className="todo_cb"
type="checkbox"
onChange={e => this.onChange(e, id)}
checked={completed ? "checked" : ""}
/>
{id}) {title}
</p>
</div>
);
}
}
// PropTypes
TodoItem.propTypes = {
todo: PropTypes.object.isRequired
};
const mapDispatchToProps = dispatch => ({
checkTodo: todo => dispatch(checkTodo(todo))
});
const mapStateToProps = state => ({});
export default connect(
null,
mapDispatchToProps
)(TodoItem);
I noticed that if I do pass a mapStateToProps in child comp it is re-rendering, like this:
const mapStateToProps = state => ({
some_prop: state
});
I understand the if I use mapStateToProps in the child it re-renders but I don't need anything directly from the state in child, the parent does this.
It makes some sense but my todos are stored in an Array in the state and I am mapping over them as you see in the parent component, so I can't map a specific todo from this array to the component props (how could I distinguish each element in the array to map to the prop?).
I am very confused.
I read that component re-renders when state or his props change. Inside the child component the props do change because the parent re-renders and it iterates the todos again and return the component with new props.
Maybe it's not the way to pass the todos to the components but I still don't understand how come the props changes and render() is not called.
Thank you very much!
Edit 1:
I connected the checkTodo action to the parent component and passed the function with props and it works just fine.
Still I don't understand why before the child component haven't re-rendered with the previous code...
Edit 2:
Actually I just lied, it does not work. I forgot to remove mapStateToProps which I said worked, so I am back to square one.
Edit 3:
Solved with by calling forceUpdate(). Still can't understand why it happened.
we get dispatch function ref in our connected component but when we use action creator argument in our connect function then it will not return dispatch function as previously.
Case 1. With only first param of connect function.
import React, { Component } from 'react';
import { connect } from 'react-redux';
class App extends Component {
render() {
console.log('this.props',this.props)
return (
<div className="App">
App
</div>
);
}
}
const mapStateToProps = (state) => {
return state
}
export default connect(mapStateToProps)(App);
Case 2. Use connect's second parameter also
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setData } from './actions'
class App extends Component {
render() {
console.log('this.props',this.props)
return (
<div className="App">
App
</div>
);
}
}
const mapStateToProps = (state) => {
return state
}
export default connect(mapStateToProps,{ setData })(App);
I expect that in second case, dispatch function ref should also be listed in props, but its not happend .
Console output :
case 1. dispatch is showing in props https://prnt.sc/ne0fpb
case 2. No dispatch get in props https://prnt.sc/ne0djb
So please help me why i am not getting dispatch function in case 2?
Second connect parameter (mapDispatchToProps) defaults to dispatch => ({ dispatch }) function. Default value isn't applied when it's specified.
Since mapDispatchToProps is used to provide all needed dispatcher functions as component props, dispatch prop isn't needed.
//action code
export const CLEAR_COMPLETED = 'CLEAR_COMPLETED'
export const clearCompleted = () => {
return{
type: CLEAR_COMPLETED
}
}
//reducer code
case CLEAR_COMPLETED:
return state.map(todo => {if (todo.completed)
{return {...todo, show:false}}
else {return todo}})
Problem dispatching action on Todo application in react-redux.
import React from 'react'
import { connect } from 'react-redux'
import { clearCompleted } from '../actions'
const ClearButton = ({dispatch}) => {
return(
<button fluid onClick={e => {dispatch(clearCompleted())}}>
Clear Completed
</button>
)
}
export default ClearButton
Trying to change the store by clicking on Clear Completed Button. Clear Completed Button should remove the completed todos from the store and todo list should be updated. I am trying to call 'clearCompleted' action with Clear Completed Button.
The difficulty you're having here is that your component doesn't know anything about the Redux store, and the dispatch function will not be in its props. The most basic way you can make dispatch available would be this:
export default connect()(ClearButton)
This will allow you to use dispatch(clearCompleted()) without messing around further with mapDispatchToProps. You'd have to change its definition so it's not a stateless component though.
However, you should probably ask yourself whether a tiny button really needs connect at all? You could probably just pass the correct function down from the containing component:
// TodoList.js
class TodoList extends Component {
render () {
return (
...
<ClearButton clearCompleted={this.props.clearCompleted} />
)
}
}
const mapStateToProps = state => ({
// ...
})
const mapDispatchToProps = dispatch => ({
clearCompleted: () => dispatch(clearCompleted())
})
export default connect(mapStateToProps, mapDispatchToProps)(TodoList)
Then the function will be in ClearButton's props without it needing to be connected:
<button onClick={this.props.clearCompleted}>
You can do it by wrapping your component in connect.
connect accepts two arguments as first call, mapStateToProps for mapping your store properties into your component's props and mapDispatchToProps for mapping action creators into your component's props. It's also followed by another call to that function with the Component name of yours written in class syntax.
If you insist in using stateless components with connect, you can use compose utility from redux.
import React from 'react'
import {bindActionCreators} from 'redux';
import { connect } from 'react-redux'
import { clearCompleted } from '../actions'
class ClearButton extends React.Component {
render() {
const {clearCompleted} = this.props;
return(
<button fluid onClick={clearCompleted}>
Clear Completed
</button>
)
}
}
const mapDispatchToProps = dispatch => bindActionCreators({ clearCompleted }, dispatch);
export default connect(null, mapDispatchToProps)(ClearButton);
I have a problem that a react component is rendering before the redux store has any data.
The problem is caused by the React component being rendered to the page before the existing angular app has dispatched the data to the store.
I cannot alter the order of the rendering or anything like that.
My simple React component is
import React, {Component} from 'react';
import { connect } from 'react-redux';
import {addBot} from './actions';
class FlowsContainer extends React.Component {
componentDidMount() {
this.props.initStoreWithBot();
}
render() {
// *** at this point I have the store in state prop
//but editorFlow array is not yet instanced, it's undefined
const tasks = this.props.state.editorFlow[0].flow.tasks
return (
<div>
Flow editor react component in main container
</div>
);
}
}
const mapStateToProps = (state) => ({
state : state
})
const mapDispatchToProps = (dispatch) => {
return {
initStoreWithBot : () => dispatch(addBot("test 123"))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FlowsContainer)
So how can I hold off the rendering until editorFlow array has elements ?
You can use Conditional Rendering.
import {addBot} from './actions';
class FlowsContainer extends React.Component {
componentDidMount() {
this.props.initStoreWithBot();
}
render() {
// *** at this point I have the store in state prop
//but editorFlow array is not yet instanced, it's undefined
const { editorFlow } = this.props.state;
let tasks;
if (typeof editorFlow === 'object' && editorFlow.length > 0) {
tasks = editorFlow[0].flow.tasks;
}
return (
{tasks &&
<div>
Flow editor react component in main container
</div>
}
);
}
}
const mapStateToProps = (state) => ({
state : state
})
const mapDispatchToProps = (dispatch) => {
return {
initStoreWithBot : () => dispatch(addBot("test 123"))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FlowsContainer)
As far as I know, you can't.
the way redux works is that it first renders everything, then actions take place with some async stuff(such as loading data), then the store gets populated, and then redux updates the components with the new state(using mapStateToProps).
the lifecycle as I understand it is this :
render the component with the initial state tree that's provided when you create the store.
Do async actions, load data, extend/modify the redux state
Redux updates your components with the new state.
I don't think mapping the entire redux state to a single prop is a good idea, the component should really take what it needs from the global state.
Adding some sane defaults to your component can ensure that a "loading" spinner is displayed until the data is fetched.
In response to Cssko (I've upped your answer) (and thedude) thanks guys a working solution is
import React, {Component} from 'react';
import { connect } from 'react-redux';
import {addBot} from './actions';
class FlowsContainer extends React.Component {
componentDidMount() {
this.props.initStoreWithBot();
}
render() {
const { editorFlow } = this.props.state;
let tasks;
if (typeof editorFlow === 'object' && editorFlow.length > 0) {
tasks = editorFlow[0].flow.tasks;
}
if(tasks){
return (
<div>
Flow editor react component in main container
</div>
)
}
else{
return null;
}
}
}
const mapStateToProps = (state) => ({
state : state
})
const mapDispatchToProps = (dispatch) => {
return {
initStoreWithBot : () => dispatch(addBot("test 123"))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(FlowsContainer)
Got undefined when trying to access this.props.state value
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
const mapStateToProps = ({ movies }) => ({
state: movies,
});
class App extends Component {
componentDidMount() {
this.props.addMovie({ title: 'Movie' }); //redux action
}
render() {
const { state } = this.props;
console.log(state.test); //Object {title: "Movie"}
console.log(state.test.title); //Uncaught TypeError: Cannot read property 'title' of undefined
return (
<div>Hello {state.test.title}!</div> //doesn't work
);
}
}
export default connect(mapStateToProps, actions)(App);
After redux complete the action i've got state object in component props
I can access it from Chrome DevTools $r.props.state.test.title but can't access it from render function
For example I can get value from this.props.params.movieID but can't get any params modified by mapStateToProps
How can I put this.props.state.test.title value in my div?
Inside your render function check if the title is undefined because the render function is being called before your redux action is resolving. Once your state is updated and your component renders it should show up the second time.
render() {
const { state } = this.props;
console.log(state.test);
if(state.test && state.test.title){
console.log(state.test.title);
return (
<div>Hello {state.test.title}!</div>
)
} else {
return (
<div>No movies listed</div>
);
}
}