'dispatch' is not defined - reactjs

I've been debugging this program but with no clue, I followed this tutorial word by word trying to make a TODO app but I could not figure out why I am getting this error message.
./src/containers.js
Line 12: 'dispatch' is not defined no-undef
Line 13: 'dispatch' is not defined no-undef
components.js
import React from 'react'
class Todo extends React.Component {
render() {
const { todo } = this.props
if (todo.isDone) {
return <strike>{todo.text}</strike>
} else {
return <span>{todo.text}</span>
}
}
}
export default class TodoList extends React.Component {
render() {
const {todos, toggleTodo, addTodo } = this.props
console.log(toggleTodo)
return (
<div className="todo">
<input type="text" placeholder="Add todo"/>
<ul className='todo__list'>
{todos.map(t => (
<li key={t.id} className='todo__item'>
<Todo todo={t} />
</li>
))}
</ul>
</div>
)
}
}
containers.js
import * as components from './components.js'
import { addTodo, toggleTodo } from './actions.js'
import { connect } from 'react-redux'
const mapStateToProps = (state) => {
return {todos: state}
}
const mapDispatchToProps = (state) => {
return {
addTodo: text => dispatch(addTodo(text)),
toggleTodo: id => dispatch(toggleTodo(id))
}
}
const TodoList = connect(mapStateToProps, mapDispatchToProps)(components.TodoList)
export default TodoList
reducers.js
const reducer = (todos = [], action) => {
switch(action.type) {
case 'ADD_TODO': {
return [...todos, {id: action.id, text: action.text, completed: false}]
}
case 'TOGGLE_TODO': {
return todos.map(todo => todo.id === action.id ? {...todo, completed: !todo.completed} : todo)
}
default: {
return todos
}
}
}
export default reducer
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { TodoList } from './containers'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import reducer from './reducers'
const store = createStore(reducer)
ReactDOM.render(
<Provider store={store}>
<TodoList />
</Provider>,
document.getElementById("app")
)

Instead of state here:
mapDispatchToProps = (state) =>
Use dispatch:
mapDispatchToProps = (dispatch) =>
In containers.js.
DOC:
container components can dispatch actions. In a similar fashion, you
can define a function called mapDispatchToProps() that receives the
dispatch() method and returns callback props that you want to inject
into the presentational component.

Related

Pass text value to another component

How to pass text value to another component using Redux in React?
I am learning Redux in React. I am trying to pass text value to another component using Redux in React.
My code is like below
Mycomponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Mycomponent extends Component {
state = {
textInput: '',
}
handleChange = event => {
this.props.dispatch({ type: "add" });
}
render = () => {
return (
<div>
<input
type="text"
onChange={this.handleChange} />
</div>
);
}
}
const mapStateToProps = state => ({ nameState: state.nameState});
export default connect(mapStateToProps)(Mycomponent);
nameAction.js
export const nameAction = () => ({
type: 'add'
});
export default { nameAction };
nameReducer.js
const nameReducer = (state = {}, action) => {
switch (action.type) {
case 'add': {
return {
...state,
nameState: action.payload
};
}
default:
return state;
}
};
export default nameReducer;
Outputcomponent.js
import React, { Component } from 'react';
class Outputcomponent extends Component {
render = (props) => {
return (
<div>
<div>{this.props.nameState }</div>
</div>
);
}
}
export default Outputcomponent;
The use of redux hooks explained by Josiah is for me the best approach but you can also use mapDispatchToProps.
Even if the main problem is that you don't pass any data in your 'add' action.
nameAction.js
You call the action.payload in nameReducer.js but it does not appear in your action
export const nameAction = (text) => ({
type: 'add',
payload: text
});
Mycomponent.js
Then as for your state we can mapDispatchToProps.
(I think it's better to trigger the action with a submit button and save the input change in your textInput state, but I guess it's intentional that there is none)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {nameAction} from './nameAction'
class Mycomponent extends Component {
state = {
textInput: '',
}
handleChange = event => {
this.props.nameAction(event.target.value);
}
render = () => {
return (
<div>
<input
type="text"
onChange={this.handleChange} />
</div>
);
}
}
const mapStateToProps = state => ({ nameState: state.nameState});
const mapDispatchToProps = dispatch => ({ nameAction: (text) => dispatch(nameAction(text))});
export default connect(mapStateToProps,mapDispatchToProps)(Mycomponent);
OutputComponent.js
to get the data two possibilities either with a class using connect and mapStateToProps , or using the useSelector hook with a functional component.
with a Class
import React, { Component } from "react";
import { connect } from "react-redux";
class OutputComponent extends Component {
render = () => {
return (
<div>
<div>{this.props.nameState}</div>
</div>
);
};
}
const mapStateToProps = state => state;
export default connect(mapStateToProps)(OutputComponent);
with a functional component
import React from "react";
import { useSelector } from "react-redux";
const OutputComponent = () => {
const nameState = useSelector((state) => state.nameState);
return (
<div>
<div>{nameState}</div>
</div>
);
};
export default OutputComponent;
Of course you must not forget to create a strore and to provide it to the highest component
store.js
import { createStore } from "redux";
import nameReducer from "./nameReducer";
const store = createStore(nameReducer);
export default store;
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Component
const AddTodo = () => {
const [todo, setTodo] = useState("");
const dispatch = useDispatch();
const handleChange = (e) => setTodo(e.target.value);
const handleSubmit = (e) => {
e.preventDefault();
dispatch(addTodoAction(todo));
}
return {
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
</form>
}
)
Actions
const addTodoAction = (text) => {
dispatch({
type: "ADD_TODO",
payload: text
})
}
Reducers
const addTodoReducer = (state, action) => {
switch(action.type) {
case "ADD_TODO":
return {
todo: action.payload,
}
default:
return state;
}
}
store
// some code for store.js
Accessing this todo from another component
const ComponentA = () => {
const {todo} = useSelector(state => state.todo);
return (
<p> {todo} </p>
)
}
Side Note:
Redux comes with too much boilerplate if you want to pass text from one component to another, just use props

In my redux store array, I have an undefined item at 0 index. I am not sure how it is there?

I'm learning redux. This is a simple project for understanding react redux. The basic idea is to have two components at the same level, in component1 we add the items, keep it in store and in component2 we display the items from the store.
Code for component 1
import React,{useState} from 'react';
import store from "../store/index";
import { connect } from "react-redux";
const Component1 = (props) => {
let [inputTab, handleInputTab] = useState("");
return ( <div>
<input onChange={(e)=>{
handleInputTab(e.target.value);
}}
value={inputTab}
/>
<button onClick={()=>{
props.addItem({inputTab});
console.log(store.getState());
handleInputTab("")
}}>Add</button>
</div> );
}
const mapDispatchToProps = dispatch => {
return {
// dispatching plain actions
addItem: (listItem) => dispatch({ type: 'ADD_ITEM', payload: listItem }),
}
}
export default connect(null,mapDispatchToProps)(Component1);
Code for component 2
import React from 'react';
import { connect } from "react-redux";
const Component2 = (props) => {
if(props.length <= 1){
return <div>tetst</div>
}
return ( <div>
{props.list.map((item, index) => (
item != undefined && <p key={index}>{item.inputTab}</p>
))}
</div>
);
}
const mapStateToProps = state =>{
return {
list: state.list
}
}
export default connect(mapStateToProps)(Component2);
Action
const ADD_ITEM = "ADD_ITEM";
export function addItem(payload) {
return {
type: ADD_ITEM,
payload
};
}
Reducer
const initialState = {
list:[]
}
function addItemReducer(state = initialState, action){
if(action.type="ADD_ITEM"){
return {
list: [...state.list, action.payload]
}
}
return state;
}
export default addItemReducer
Store
import { createStore } from "redux";
import addItemReducer from "../reducers/index"
export default createStore(addItemReducer,);
Problem
On console logging the state, I notice an undefined item at 0 index of the list array. I am not sure where it is coming from.
{list: Array(3)}
list: Array(3)
0: undefined
1: {inputTab: "a"}
2: {inputTab: "b"}
length: 3
__proto__: Array(0)
__proto__: Object

TypeError: todos.map is not a function

I am building a todo app with React, Redux and TypeScript with hooks.
I cannot figure out why it succesfully compiles but fails to run in the browser.
Error:
TypeError: todos.map is not a function.
I have checked the type of todo which is of type Todo[] (i.e. an array) and, correspondingly, it can iterate using map.
I am not sure how to fix this.
Code here: https://codesandbox.io/s/agitated-meadow-53w0u?file=/src/App.tsx
App.tsx
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { useTypedSelector } from "./index";
function AddToDo() {
const [input, setInput] = useState("");
const dispatch = useDispatch();
function handleInput(e: React.ChangeEvent<HTMLInputElement>) {
setInput(e.target.value);
}
//dispatch to store
function handleAddTodo() {
dispatch({ text: input })
setInput("");
}
return (
<div>
<input type="text" onChange={e => handleInput(e)} value={input} />
<button type="button" onClick={handleAddTodo}>
Add todo
</button>
</div>
);
}
//TodoList
export interface Todo {
text: string;
}
function TodoList() {
const todos = useTypedSelector((state) => state)
return (
<ul className="todo-list">
{todos.map((todo: Todo) => {
return <Todo todo={todo} />;
})}
</ul>
);
}
//Todo
function Todo({ todo }: { todo: Todo }) {
return <li>{todo.text}</li>;
}
function App() {
return (
<div className="App">
<AddToDo />
<TodoList />
</div>
);
}
export default App;
index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider, TypedUseSelectorHook, useSelector } from 'react-redux'
import { createStore, combineReducers } from 'redux'
import App, { Todo } from './App';
//actions
const ADD_TODO = 'ADD_TODO'
type Action = AddTodo
export function addTodo(text: string) {
return {
type: ADD_TODO,
text
}
}
type AddTodo = ReturnType<typeof addTodo>
const INITIAL_STATE = [] as Todo[]
//reducer
function todoReducer(state = INITIAL_STATE, action: Action): Todo[] {
switch (action.type) {
case ADD_TODO:
const todos: Todo[] = [
...state,
{
text: action.text,
}
]
return todos
default:
return state
}
}
const todoApp = combineReducers({
todos: todoReducer
}
)
type RootState = ReturnType<typeof todoReducer>
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector
//store
const store = createStore(todoApp)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
As per #JohnHass
because RootState isn't the actual type of the root state, you've made
it Todo[] when the state is actually { todos: Todo[] }
Then you need to change is from this
const todos = useTypedSelector(state => state);
To :
const todos = useTypedSelector(state => state.todos);
state was not returning array but object with todos key,
Another issue :
Not passing event type :
function handleAddTodo() {
dispatch({ text: input }); // <--- Not even passing action type
setInput("");
}
Change it to :
function handleAddTodo() {
dispatch({ type: "ADD_TODO", text: input });
setInput("");
}
//-------------- OR ---------------
import { useTypedSelector , addTodo } from "./index";
//dispatch to store
function handleAddTodo() {
dispatch(addTodo(input));
setInput("");
}
Issue
Your state.todos is an Array, but you are assigning const todos to the entire state, that is an object { todos: Todo[], ... }.
Possible solutions
1. Get just todos, requesting just that property inside the callback:
const todos = useTypedSelector((state) => state.todos)
2. Deconstruct the assignment:
const { todos } = useTypedSelector((state) => state)

Could not find store in either the context or props of connect site redux error

I am trying to create something similar to todo app with react and redux from here.I have been reading all the solutions for this problem and nothing seems to be working for my case.
Most of the solutions purpose using Provider which I am already using. Any help is much appreciated.
Edit - Few import statement might be missing in snippets, but all components are being imported whenever needed and actions are also defined in action file.
Index.js
import App from './components/App'
import reducer from './reducers/index'
const store = createStore(reducer)
const AppWithStore = (
<Provider store={store}>
<App />
</Provider>
)
ReactDOM.render(AppWithStore, document.getElementById('root'))
Update - Combined Reducer code
import React from 'react'
import { combineReducers } from 'redux'
import TestReducer from './TestReducer'
export default combineReducers({
TestReducer,
})
App.js
import Test from './Test';
class App extends Component {
render() {
return (
<Test />
);}
}
ReactDOM.render(
(<App/>),
document.getElementById("root")
);
export default App
Test.js contains both component and container
import { connect } from 'react-redux'
import { add } from '../actions'
const mapStateToProps = state => ({
todos: state.todos,
})
class Test extends Component {
dosomething() {
const dispatch = this.props;
dispatch(add("New Note"));
}
render() {
return (
<div>
< button OnClick = { this.dosomething.bind(this)} > Test </button>
</div>
)
}
}
export default connect(mapStateToProps)(Test)
The reducer logic for Test is as given below
import React from 'react';
const TestReducer = (state = [], action) => {
const todos = state;
const {type, payload} = action;
switch(action.type){
case 'ADD': {
return {
...state,
todos:"new todo"
}
}
}
return state;
}
export default TestReducer
You should remove
ReactDOM.render(
(<App/>),
document.getElementById("root")
); from App.js file
When you call it again in App.js a new component instance independent of others is created, That's why it is not finding store.As store is not passed to it.
You can check it here https://codesandbox.io/s/vy7wwqw570. As i had remove render api call from app.js it is working now.
you need to import your reducers so that you
if your file name is TestReducer.js
import React from 'react';
export const TestReducer = (state = [], action) => {
const todos = state;
const {type, payload} = action;
switch(action.type){
case 'ADD': {
return {
...state,
todos:"new todo"
}
}
}
return state;
}
then import it in this manner
import {TestReducer} from './TestReducer.js';
const store = createStore(TestReducer);
Try replacing todos: state.todos with todos: state.TestReducer.todos
import { connect } from 'react-redux'
import { add } from '../actions'
const mapStateToProps = state => ({
//todos: state.todos,
todos: state.TestReducer.todos
})
class Test extends Component {
dosomething() {
const dispatch = this.props;
dispatch(add("New Note"));
}
render() {
return (
<div>
< button OnClick = { this.dosomething.bind(this)} > Test </button>
</div>
)
}
}
export default connect(mapStateToProps)(Test)

How to re-render components when change in store in Redux?

I'm trying to make my first application with Redux, I've already made a version of this without Redux and I know that Redux isn't necessarily needed for this but I want to learn Redux.
I have a store which has an array of to-do items, my action successfully dispatches and updates the store.
My list of task components connects to the store and renders each item in the array as its own component.
On initial load, my to-do list shows the to-do items from the store's initial state, but once I update the state the new items from the state do not get rendered. Instead the map method to return the array of components says it 'cannot read property 'map' of undefined'.
How do I solve this?
Cheers.
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import Container from './components/Container.js'
import TaskReducer from './reducers/Task.js'
require("./index.css");
const defaultState = {
items: [
"task 1",
"task 2"
]
};
const store = createStore(TaskReducer, defaultState);
// Allows access to store in console log
window.store = store;
ReactDOM.render( (
<Provider store={store}>
<Container />
</Provider>
),
document.getElementById('wrapper')
);
import React from 'react';
import ReactDOM from 'react-dom';
import TaskList from './TaskList.js';
import { createStore, bindActionCreators } from 'redux';
import * as ActionCreators from '../actions/Task.js';
import Redux from 'redux';
import {connect} from 'react-redux'
class Container extends React.Component {
constructor() {
super();
}
render() {
// What does this do???
const {dispatch} = this.props;
const deleteItem = bindActionCreators(ActionCreators.deleteTodoItem, dispatch);
const addItem = bindActionCreators(ActionCreators.addTodoItem, dispatch);
function _onSubmit(e) {
e.preventDefault();
addItem(e.target.elements.task.value);
// Resets the form
e.target.reset();
}
return (
<div className="">
<header className="header">
<h1>To Do:</h1>
</header>
<form autoComplete="off" onSubmit={_onSubmit}>
<input name="task" placeholder="Task" autoComplete="off"></input>
</form>
<TaskList />
</div>
);
}
}
const mapStateToProps = state => (
{
items: state.items
}
);
export default connect(mapStateToProps)(Container);
import React from 'react';
import Task from './Task';
import { connect } from 'react-redux';
let TaskList = (props) => {
console.log('items', props.items);
var tasks = (props.items).map( (item, key) => { return <Task data={item} key={key} listItemKey={key} /> })
return(
<ul className="task-list">
{tasks}
</ul>
);
}
const mapStateToProps = state => (
{
items: state.items
}
);
export default connect(mapStateToProps)(TaskList);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
import * as action from '../actions/Task.js'
export default function toDoItems(state = [], action) {
switch(action.type) {
case 'DELETE_ITEM':
return [
...state,
];
case 'ADD_ITEM':
console.log('ADD ITEM');
console.log('Submitted value = ', action.submittedValue)
return [
...state,
// Reducer gets action object item and appends to array
action.submittedValue
]
default:
return state;
}
}
--- Reducer ---
import * as action from '../actions/Task.js'
export default function toDoItems(state = [], action) {
switch(action.type) {
case 'DELETE_ITEM':
return [
...state,
];
case 'ADD_ITEM':
console.log('ADD ITEM');
console.log('Submitted value = ', action.submittedValue);
console.log('the state', state);
return [
...state,
// Reducer gets action object item and appends to array
action.submittedValue
]
default:
return state;
}
}
--- Action ---
export function addTodoItem(submittedValue) {
return {
type: 'ADD_ITEM',
// The action object returned has the submittedValue
submittedValue
}
}
export function deleteTodoItem() {
return {
type: 'DELETE_ITEM',
}
}
I have edited the TaskList component. You are not using the map function correctly
import React from 'react';
import Task from './Task';
import { connect } from 'react-redux';
let TaskList = (props) => {
console.log('items', props.items);
var tasks = undefined;
if(props.items && props.items.length > 0 ){
tasks = props.items.map( (item, key) => { return <Task data={item}
key={key} listItemKey={key} /> })
} //edited code
return(
<ul className="task-list">
{tasks}
</ul>
);
}
const mapStateToProps = state => (
{
items: state.items
}
);
export default connect(mapStateToProps)(TaskList);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
And as for the store use the following to configure store in your main file.
...
import TaskReducer from './reducers/Task.js';
import * as redux from 'redux';
function configure(initialState = {}){
const reducers = redux.combineReducers({
tasks : TaskReducer
});
let store = redux.createStore(reducers, initialState);
return store;
};
const store = configure();
// Allows access to store in console log
window.store = store;
ReactDOM.render( (
<Provider store={store}>
<Container />
</Provider>
),
document.getElementById('wrapper')
);

Resources