Cannot fire my action from the container page, props undefined error - reactjs

I am getting “TypeError: Cannot read property 'props' of undefined”
when i try to fire my function onChange from a deeper component. I can fire the action from the DemoForm component, but then I cannot pass in my value as it becomes undefined so, I am trying to make a function that takes in the event information and then I am firing my action but it says props is undefined, when I do a debugger and check on the console, its all there
// App.js
class App extends Component {
constructor(props) {
super(props)
}
handleThis(e){
this.props.SomeAction
}
render() {
return (
<div className="App">
<DemoForm state={this.props} someFunction={this.handleThis }/>
<AnotherForm/>
</div>
);
}
}
const mapStateToProps = (reduxState) => {
return reduxState;
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(actionCreators, dispatch);
}
export default connect(
mapStateToProps, mapDispatchToProps
)(App)
// Demo.js
let DemoForm = ({ handleSubmit, submitting, state }) =>
<form onSubmit={handleSubmit(showResults)}>
<Field name="value" label="Value" component={RenderInput} onChange={(e) => this.props.someFunction(e.target.value) } />
<button type="submit"> Submit </button>
{console.log("Demo state >>>>> ", {state})}
</form>
DemoForm = reduxForm({
form: 'demo',
destroyOnUnmount: false,
validate
})(DemoForm)
export default DemoForm
// RenderInput
const RenderInput = createRenderer((input, label, onChange) => {
return <input {...input}/>
})
export default RenderInput
// createRenderer
const createRenderer = render => ({ input, meta, label, ...rest }) => {
return (
<div>
{/* <pre> {JSON.stringify(input, null, 2) }</pre> */}
<label> {label}</label>
{render(input, label, rest)}
{
meta.touched &&
<span className="text-danger"> {meta.error} </span>
}
</div>
)
}
export default createRenderer
// REDUCER
const initialState = {
todos: [],
count: 0,
demoPercent: 0,
anotherPercent : 0
}
export default function rootReducer(state = initialState, action) {
if(action.type === "INC"){
console.log("incrementing count")
let newState = {...state}
newState.count++
return {
...newState
}
}
if(action.type === "GET_PERCENT"){
console.log("getting balance percent", action.payload)
let newState = {...state}
newState.demoPercent = action.payload;
newState.anotherPercent = 100 - action.payload;
return {
...newState
}
}
return state;
}
// ACTION
export function increase(){
console.log("i am INC action firing")
return {
type: "INC"
}
}
export function getPercent(value){
console.log(value) //value is undefined
return {
type: "GET_PERCENT",
paypoad : value
}
}

You need to bind the handler in your controller, to do that just change your App component constructor to:
constructor(props) {
super(props);
this.handleThis = this.handleThis.bind(this);
}

You have to bind your function... And the best way to bind function is to use arrow functions
render() {
return (
<div className="App">
<DemoForm state={this.props} someFunction={(e) => this.handleThis(e)}/>
<AnotherForm/>
</div>
);
}
}

Related

React context: send input data to another component

I have 3 components:
Search.js, Customers.js and Customer.js
In Search.js I have an input field. I want to send whatever value entered in the field over to the Customer.js component. I thought this would be straightforward, but I was wrong ...
I have also a context.js component that stores state for the application (I don't want to use redux because I don't know it yet).
Sorry but this is gonna be a long post as I want to give the background for this specific situation:
context.js
const Context = React.createContext();
const reducer = (state, action) => {
switch (action.type) {
case "SEARCH_CUSTOMERS":
return {
...state,
customer_list: action.payload,
firstName: ''
};
default:
return state;
}
};
export class Provider extends Component {
state = {
customer_list: [],
firstName: "",
dispatch: action => this.setState(state => reducer(state, action))
};
componentDidMount() {
axios
.get("/api")
.then(res => {
console.log(res.data);
this.setState({ customer_list: res.data });
})
.catch(error => console.log(error));
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
export const Consumer = Context.Consumer;
Search.js: the input value I want to send to Customer is 'firstName'
class Search extends Component {
state = {
firstName: ""
};
onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
findCustomer = (dispatch, e) => {
e.preventDefault();
axios
.get("/api/customers", {
params: {
firstName: this.state.firstName,
}
})
.then(res => {
dispatch({
type: "SEARCH_CUSTOMERS",
payload: res.data
});
this.setState({ firstName: "" });
});
};
return (
<Consumer>
{value => {
const { dispatch } = value;
return (
<form onSubmit={this.findCustomer.bind(this, dispatch)}>
<div className="form-group">
<input
ref={input => {
this.nameInput = input;
}}
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.onChange}
/>
the Customers.js:
class Customers extends Component {
render() {
const key = Date.now();
return (
<Consumer>
{value => {
const { customer_list} = value;
if (customer_list === undefined || customer_list.length === 0) {
return <Spinner />;
} else {
return (
<React.Fragment>
<h3 className="text-center mb-4">{heading}</h3>
<div className="row">
{customer_list.map(item => (
<Customer key={item.key} customer={item} />
))}
</div>
</React.Fragment>
);
}
}}
</Consumer>
);
}
}
export default Customers;
and Finally theCustomer.js: this is where I want the input value to be displayed:
const Customer = props => {
const { customer } = props;
return (
<div className="col-md-12">
<div className="card-body">
<strong>{customer.firstName}</strong> // not working
...
}
the {customer.firstName} does not show the value.
Is is necessary to go through the intermediate Customers.js component to pass the input value?
I would like to keep the architecture as is (with the context.js) and display the value in the Customer.js component.

React Redux how to pass data from input to rest api

I am trying to learn react redux and I creating a small todo app, which has backend as REST server
most of the part is implemented, however, I am not able to understand how to pass a value from my input box to rest API call. I am able to successfully store the value of inputbox in redux state.
I am using react-thunk as middleware to handle API calls.
container
import { connect } from 'react-redux'
import {toggleAddTodoDialog, addNewTodo, handleChange} from '../actions'
import AddTodoDialog from '../components/add_todo_dialog';
class AddTodoContainer extends Component {
render() {
return (
<div>
<AddTodoDialog toggleAddTodoDialog={this.props.toggleAddTodoDialog}
addNewTodo = {this.props.addNewTodo}
newTodoList = {this.props.newTodoList}
handleChange = {this.props.handleChange}
is_add_todo_dialog_opened={this.props.is_add_todo_dialog_opened}/>
</div>
)
}
}
const mapStateToProps = (state) => {
return state
}
const bindActionsToDispatch = dispatch =>
(
{
toggleAddTodoDialog : (e) => dispatch(toggleAddTodoDialog(e)),
handleChange: (e) => dispatch(handleChange(e)),
addNewTodo : (e) => addNewTodo(e)
}
)
export default connect(mapStateToProps, bindActionsToDispatch)(AddTodoContainer)
component
export default class AddTodoDialog extends Component {
toggleAddTodoDialog = (e) => {
this.props.toggleAddTodoDialog(!this.props.is_add_todo_dialog_opened)
}
addNewTodo = (e) => {
this.props.addNewTodo()
this.toggleAddTodoDialog(e)
}
handleChange = (e) => {
this.props.handleChange(e)
}
render() {
return (
<div>
<Button color="primary" onClick={this.toggleAddTodoDialog}>Add new Todo</Button>
<Modal isOpen={this.props.is_add_todo_dialog_opened} >
{/* <Modal isOpen={false} > */}
<ModalHeader toggle={this.toggleAddTodoDialog}>Modal title</ModalHeader>
<ModalBody>
<FormGroup >
<Label for="Title">Task </Label>
<Input name="task"
value={this.props.newTodoList.task}
onChange={this.handleChange} />
</FormGroup>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.addNewTodo}>OK</Button>{' '}
<Button color="secondary" onClick={this.toggleAddTodoDialog}>Cancel</Button>
</ModalFooter>
</Modal>
</div>
);
}
actions
export function addNewTodo() {
console.log("addNewTodo")
return function (dispatch) {
axios.post("http://localhost:5001/todos", 'task=' + this.props.addNewTodo.task)
.then(response => {
dispatch(_addTodoAction(response.data))
})
}
}
export function _addTodoAction(todos) {
console.log("_addTodoAction")
return {
type: 'ADD_TODO',
todos: todos
}
}
export function handleChange(event){
console.log("handleChange "+event)
return{
type: 'HANDLE_CHANGE',
event: event
}
}
reducer
case 'ADD_TODO':
console.log(action)
return {
...state,
todos: action.todos
}
You are dispatching like this:
addNewTodo : (e) => addNewTodo(e)
But your action doesn't take any arguments:
export function addNewTodo() {
console.log("addNewTodo")
return function (dispatch) {
axios.post("http://localhost:5001/todos", 'task=' + this.props.addNewTodo.task)
.then(response => {
dispatch(_addTodoAction(response.data))
})
}
}
If you want the value of the value of the input:
handleChange = (e) => {
this.props.handleChange(e.target.value)
}
And then dispatch:
handleChange: (value) => dispatch(handleChange(value)),
And then in the action:
export function handleChange(value) {
...

React-Redux: Cannot read property 'map' of undefined when deleting an item

I have an error after clicking the delete button saying:
Cannot read property 'map' of undefined.
I'm new in React Redux JS.
Please see my code below of my component reducers and actions:
Post.js
class Post extends Component {
constructor(){
super();
this.deletePost = this.deletePost.bind(this);
}
deletePost(postId){
this.props.deletePost(postId);
}
render(){
const postItems = this.props.posts.map(post => (
<div key={post.id} className="row">
<div className="container">
<h3>{post.title}</h3>
<p>{post.body}</p>
<button
onClick={() =>this.deletePost(post.id)}
className="btn btn-danger">
Delete
</button>
</div>
</div>
))
const divStyle = {
padding: '15px',
}
return (
<div style={divStyle}>
<PostForm />
<hr/>
{postItems}
</div>
)
}
}
const mapStateToProps = state => ({
posts: state.posts.items,
newPost: state.posts.item
})
export default connect(mapStateToProps, { fetchPosts, deletePost })(Post);
PostAction.js (Here is my delete action. I am using jsonplaceholder API post.)
export const deletePost = (postId) => dispatch => {
fetch('https://jsonplaceholder.typicode.com/posts/'+postId, {
method: 'DELETE',
})
.then(dispatch({
type: DELETE_POST,
payload: postId
}));
}
PostReducer.js (This is my reducer.)
case DELETE_POST:{
const newState = Object.assign([], state);`enter code here`
const filteredItems = newState.items.filter(items => {
return items.id != action.payload;
});
return filteredItems;
}
case DELETE_POST:{
const { items } = state;
const filteredItems = items.filter(items => {
return items.id != action.payload;
});
return {
...state,
items: [ ...filteredItems ]
};
}
Yes just replace
return filteredItems; to return { items: filteredItems }
But please can you check my code if it's correct. Thanks

Cannot access updated state from mapStateToProps, undefined

I'm new to redux and am having trouble accessing state from mapStateToProps. I'm trying to create a 'folder' when the user enters the folder name and submits. I've managed to update the state with an array of folder names but can't manage to access the folder array and use this to create my Folder components.
Here is a container component that is supposed to give access to 'folders' in my Folders component:
import { connect } from 'react-redux';
import Folders from './Folders';
const mapStateToProps = state => {
return {
folders: state.folders
}
}
const Cabinet = connect(
mapStateToProps
)(Folders);
export default Cabinet;
Here is the component im trying to access the state from:
import React from 'react';
import Folder from './Folder';
import AddFolderButton from './AddFolderButton';
const Folders = ({ folders }) => (
<div className="Folders">
<h2>Folders</h2>
<div className="Drawer">
{console.log(folders)}
<AddFolderButton />
</div>
</div>
)
export default Folders;
'folders' is always undefined when I update data in the store.
I'm not quite sure what I'm doing wrong, I've been working through the basics tutorial in the Redux docs but think I may be fundamentally misunderstanding something.
Here's the code I used to update the store:
Reducer
import { combineReducers } from 'redux';
const initialState = {
folders: []
}
function handleFolders(state = initialState, action) {
switch(action.type) {
case 'CREATE_FOLDER':
return {
...state, folders: [
...state.folders,
{
name: action.name
}
]
}
default:
return state;
}
}
let rootReducer = combineReducers({
handleFolders
})
export default rootReducer;
The button to 'create' a folder:
class AddFolderButton extends React.Component {
constructor() {
super();
this.state = {
isClicked: false,
};
this.handleClick = this.handleClick.bind(this);
this.handleOutsideClick = this.handleOutsideClick.bind(this);
this.textInput = null;
}
handleClick() {
if(!this.state.isClicked) {
document.addEventListener('click', this.handleOutsideClick, false);
} else {
document.removeEventListener('click', this.handleOutsideClick, false);
}
this.setState(prevState => ({
isClicked: !prevState.isClicked
}));
}
handleOutsideClick(e) {
if(this.node.contains(e.target)) {
return;
}
this.handleClick();
}
render() {
return(
<div className="new-folder-input" ref={ node => {this.node = node;}}>
{!this.state.isClicked && (
<button className="add-folder-btn" onClick={this.handleClick} >
<FontAwesomeIcon icon={faPlus} size="4x"/>
</button>
)}
{this.state.isClicked && (
<div className="folder-input">
<form onSubmit={e => {
e.preventDefault()
this.props.createFolder(this.textInput.value)
this.handleClick();
}}
>
<input ref={node => this.textInput = node}
type="text"
value={this.state.value}
autoFocus
placeholder="Enter folder name" />
<button type="submit">Add Folder</button>
</form>
</div>
)}
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
createFolder: name => dispatch(createFolder(name))
})
export default connect(
null,
mapDispatchToProps
)(AddFolderButton);
Try changing your mapStateToProps method to this:
const mapStateToProps = ({handleFolders: {folders}}) => ({
folders
});
When your're combining your reducers you must access them first using it's name. Your reducer is called handleFolders and it has property folders

React re-renders whole app after rendering a component

I use react and redux in my web app. It's the simple app which has 4 components, one reducer and 3 actions. After I add a new entry to list, react renders component of list (the listItem), then re-renders the whole app. What is the cause of re-rendering whole app after rendering one component?
Updated:
App container:
class App extends Component {
static propTypes = {
groups: PropTypes.array.isRequired,
actions: PropTypes.object.isRequired
};
render() {
return (<div>
<Header addGroup={this.props.actions.addGroup} />
<List groups={this.props.groups} />
</div>
);
}
}
function mapStateToProps(state) {
return { groups: state.groups };
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(AppActions, dispatch) };
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
Reduser:
export default function groupDiseases(state = initialState, action){
switch (action.type) {
case ADD_GROUP:
return [
{
id: '',
name: action.name
},
...state
];
case DELETE_GROUP:
return state.filter(group =>
group.id !== action.id
);
case EDIT_GROUP:
return state.map(group => (group.id === action.id ? { id: action.id, name: action.name } : group));
default:
return state;
}
}
Components:
export default class Add extends Component {
static propTypes = {
addGroup: PropTypes.func.isRequired
}
componentDidMount() {
this.textInput.focus();
}
handleAdd = () => {
const name = this.textInput.value.trim();
if (name.length !== 0) {
this.props.addGroup(name);
this.textInput.value = '';
}
}
render() {
return (
<form className="add_form">
<input
type="text"
className="add__name"
defaultValue=""
ref={(input) => this.textInput = input}
placeholder="Name" />
<button
className="add__btn"
ref="add_button"
onClick={this.handleAdd}>
Add
</button>
</form>
);
}
}
export default class ListGroups extends Component {
static propTypes = {
groups: PropTypes.array.isRequired
};
render() {
let data = this.props.groups;
let groupTemplate = <div> Группы отсутствуют. </div>;
if (data.length) {
groupTemplate = data.map((item, index) => {
return (
<div key={index}>
<Item item={item} />
</div>
);
});
}
return (
<div className="groups">
{groupTemplate}
<strong
className={'group__count ' + (data.length > 0 ? '' : 'none')}>
Всего групп: {data.length}
</strong>
</div>
);
}
}
It's likely due to the fact that you are letting the <form> continue its default behavior, which is to submit to a targeted action. Take a look at the w3c spec for buttons:
http://w3c.github.io/html-reference/button.html
Specifically, a button with no type attribute will default to submit.
So your button is telling the form to submit, with the target being the current page since none is provided. In your handleAdd method, you can do something like:
handleAdd = (event) => {
event.preventDefault(); // prevent default form submission behavior
const name = this.textInput.value.trim();
if (name.length !== 0) {
this.props.addGroup(name);
this.textInput.value = '';
}
}
Or you can modify your button to have type="button".

Resources