I have a search bar on my TODO List:
import React, { Component } from 'react';
import { FilterTasks } from '../../redux/actions/searchbar';
import reducers from '../../redux/reducers';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
class SearchBar extends Component {
render() {
const {search, value} = this.props;
return (
<input
className="form-control"
placeholder = "Filter Tasks"
onChange={(e) => FilterTasks(e.target.value)}
value={value} />
);
}
}
function mapStateToProps({tasks}) {
return {value: tasks.value};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({FilterTasks}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
Here's my action to filter:
export const SEARCH = 'SEARCH';
export function FilterTasks(value) {
return {type: SEARCH, value};
}
My search bar reducer:
import {SEARCH} from '../actions/searchbar';
const initialState = {}
export default function SEARCHREDUCER(state = initialState, action) {
switch(action.type) {
case SEARCH: {
const {value} = action;
const tasks = state.contents.filter((val) => val.includes(value));
return {...state, value, tasks};
}
default:
return state;
}
}
My Index reducer:
import { combineReducers } from 'redux';
import SEARCHREDUCER from '../reducers/searchbar';
const TaskReducer = (state = [] ,action) => {
switch (action.type){
case 'ADD_TASK':
state = state.concat(action.payload);
break;
case 'DELETE_TASK':
state = state.tasks.filter(task => task !== action.payload)
break;
}
return state;
},
reducers = combineReducers({
tasks : TaskReducer,
SEARCHREDUCER
});
export default reducers;
And my TasksList class where the filtered list should be rendered:
import React, { Component } from 'react';
import {connect} from 'react-redux';
import Task from '../task'
class TaskList extends Component {
render(){
return(
<table>
<thead>
<tr>
<th>Tasks</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{this.props.tasks.map((task,index) => <Task key={index} task={task} />)}
</tbody>
</table>
);
}
}
function MapStateToProps(state){
return{
tasks:state.tasks,
}
}
export default connect (MapStateToProps)(TaskList);
My problem here is that when I type an entry on the search bar the Tasks list does not change at all, It's not showing any kind of error. What i'm missing here?
FilterTasks(e.target.value) should be this.props.FilterTasks(e.target.value) instead, otherwise it'll call the the imported function from actions that is not bound to Redux by your mapDispatchToProps.
Also, your TaskReducer and SEARCHREDUCER are wrong. The reducer variable is the one with the combined state, not TaskReducer or SEARCHREDUCER.
You should just keep the search string in state and do the filtering within TaskList with this.props.tasks.filter(<insert filter function>).map(<insert map function>).
Related
I have been creating a todo app with Django backend and react Frontend using REACT API. The action is successfully dispatched and data fetched successfully, but in the RenderTodo Component the data has not appeared. Below are the attached files.
Definitely, there's just small thing I'm missing but can't get it!
I'm somewhat new to this thing so, need help.
Is it anything like declaring an empty state in TodoComponent.js?
I have also attached the rendered form in the browser at last.
Along with below data I have now put it on GitHub here.
Directory Structure is
Files:
configureStore.js
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import logger from 'redux-logger';
import { Todos } from './reducers/todo';
export const ConfigureStore =() => {
const store = createStore(
combineReducers({
todos: Todos,
}),
composeWithDevTools(applyMiddleware(thunk, logger))
);
return store;
}
ActionCreators.js
import * as ActionTypes from './ActionTypes';
import { baseUrl } from './baseUrl';
import axios from 'axios';
//GET_TODOS
export const getTodos = () => (dispatch) => {
axios
.get('api/todo/')
.then(res => {
dispatch({
type: ActionTypes.GET_TODOS,
payload: res.data
});
}).catch(err => console.log(err));
}
todo.js
import * as ActionTypes from '../ActionTypes';
export const initialState = {
todos: []
}
export const Todos = (state=initialState, action) => {
switch(action.type) {
case ActionTypes.GET_TODOS:
return {
...state,
todos: action.payload
};
default:
return state;
}
};
App.js
import React, { Component, Fragment } from "react";
//used for providing store to all child components
import { Provider } from "react-redux";
import Todo from "./components/TodoComponent";
import { ConfigureStore } from "./redux/configureStore";
const store = ConfigureStore();
export class App extends Component {
render() {
return (
<Provider store={store}>
<Fragment>
<Todo />
</Fragment>
</Provider>
);
}
}
export default App;
TodoComponent.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { getTodos } from "../redux/ActionCreators";
import RenderTodo from "./RenderTodoComponent";
const mapStateToProps = (state) => ({
todos: state.todos.todos,
});
const mapDispatchToProps = (dispatch) => ({
getTodos: () => {
dispatch(getTodos());
},
});
class Todo extends Component {
componentDidMount() {
this.props.getTodos();
}
render() {
return (
<div className="Main">
<h1>Todos</h1>
<RenderTodo todos={this.props.todos} />
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Todo);
RenderComponent.js
import React from "react";
export default function RenderTodo(props) {
return (
<div>
{console.log("second component")}
<table className="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Text</th>
<th>Completed</th>
<th>Created_at</th>
<th />
</tr>
</thead>
<tbody>
{props.todos.map((todo) => {
<tr key={todo.id}>
<td>{todo.id}</td>
<td>{todo.text}</td>
<td>{todo.created_at}</td>
<td>{todo.completed}</td>
<td>
<button className="btn btn-danger btn-sm">Delete</button>
</td>
</tr>;
})}
</tbody>
</table>
</div>
);
}
browser
console printing second component is a debugging statement showing the rendering of the second component and then the first components' componentDidMount is called.
EDIT: Added redux-dev tools -images
I have attached action, state and diff, hope this helps in troubleshooting. Everything seems fine here
You aren't RETURNing anything from props.todos.map
Replace the { and } with ( and ) to return the JSX.
{props.todos.map((todo) => ( // this was previously a {
<tr key={todo.id}>
<td>{todo.id}</td>
<td>{todo.text}</td>
<td>{todo.created_at}</td>
<td>{todo.completed}</td>
<td>
<button className="btn btn-danger btn-sm">Delete</button>
</td>
</tr>
))}
I am trying to display the redux state into my react component, but it comes undefined.
I am unable to understand where am I doing the mistake.
I am learning redux by trying a coding on my own by going through the redux documentation.
Main React component
import React, { Component } from 'react';
import Counter from './components/Counter';
import {Provider} from 'react-redux';
import store from './redux/store';
class App extends Component {
render() {
return (
<Provider store={store}>
<div>
<h1>COUNTER APPlICATION</h1>
<Counter />
</div>
</Provider>
)
}
}
export default App;
React Component
import React, { Component } from 'react';
import {connect} from 'react-redux';
import {addNumber} from '../redux/actions/addAction';
import {substractNumber} from '../redux/actions/substractAction';
export class Counter extends Component {
render() {
return (
<div>
<h1>Value:{this.props.value}</h1>
<h1>Add Only Value:{this.props.addOnly}</h1>
<button onClick = {() => this.props.addNumber}>+</button>
<button onClick = {() => this.props.substractNumber}>-</button>
</div>
)
}
}
const mapStateToProps = state => ({
value: state.value
});
export default connect(mapStateToProps, {addNumber, substractNumber})(Counter);
addReducer
import {ADDITION} from '../actions/actionTypes';
const initialState = {
value: 50
}
export default function (state = initialState, action) {
switch(action.type){
case ADDITION:
return{
value: state.value + 2
}
default:
return state
}
}
substractReducer
import {SUBSTRACTION} from '../actions/actionTypes';
const initialState = {
value: 50
}
export default function (state = initialState, action) {
switch (action.type) {
case SUBSTRACTION:
return {
value: state.value - 2
}
default:
return state
}
}
rootReducer
import {combineReducers} from 'redux';
import addReducer from './addReducer';
import substractReducer from './substractReducer';
export default combineReducers({
add: addReducer,
substract: substractReducer
})
store
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers/rootReducer';
import thunk from 'redux-thunk';
export default createStore(rootReducer, applyMiddleware(thunk));
action type
export const ADDITION = 'ADDITION';
export const SUBSTRACTION = 'SUBSTRACTION';
addAction
import {ADDITION} from './actionTypes';
export const addNumber = () => (dispatch) => {
return dispatch({
type: ADDITION,
payload: 2
})
}
substractAction
import {SUBSTRACTION} from './actionTypes';
export const substractNumber = () => (dispatch) => {
return dispatch({
type: SUBSTRACTION,
payload: 2
})
}
You are doing wrong.
you state is just counter value, so don't split into two reducers. You only need two case statement, one for ADD, one for SUBTRACT.
Don't use combineReducer and it you want, use one key like counter for counter reducer
in mapStateToProp, get value like state.counter.value where counter is name of key you used in combineReducer({ counter: counterReducer })
Your button actions/onclick is wrong
import {ADDITION, SUBTRACTION} from '../actions/actionTypes';
const initialState = {
value: 50
}
export default function (state = initialState, action) {
switch(action.type){
case ADDITION:
return
value: state.value + 2
}
case SUBTRACTION:
return{
value: state.value + 2
}
default:
return state
}
}
///// no need to regester 2 reducer, just add one above like this
export default combineReducers({
counter: counterReducer
});
/// In Counter component , mapStateToProp
const mapStateToProps = state => ({
value: state.counter.value
});
// Just pass redux actions to onClick in button like this
<button onClick = {this.props.addNumber}>+</button>
<button onClick = {this.props.substractNumber}>-</button>
When you combineReducers like this:
export default combineReducers({
add: addReducer,
substract: substractReducer
})
Your state tree will look like:
{
add: {
value: 0
},
subtract: {
value: 0
}
}
So you should only have a single reducer in order to reduce over the same value.
I have tried a lot but i couldn't figure out what is the issue.
The props in the component is coming as empty even after adding mapStateToProps and mapDispatchToProps property.Whenever i run the below code i get following error.
projList.js:94 Uncaught TypeError: _this2.props.addNewProj is not a function
My component class is given below:
import React from 'react';
import { addProj } from '../actions';
import { connect } from 'react-redux';
import C from '../constants';
class projList extends React.Component {
constructor(props){
super(props);
this.state = {
title: ''
}
}
render(){
const {title} = this.state;
return(
<section className='proj-list-container'>
<div className='form'>
<label>project Title</label>
<input type='text' onChange={(e)=>{this.setState({title: e.target.value})}}/>
<button className='submit' onClick={()=>{this.props.addNewProj(title)}}>submit</button>
</div>}
</section>
);
}
}
const mapStateToProps = (state, props) =>
({
projLists: state.addProjToList
})
const mapDispatchToProps = dispatch =>
({
addNewProj(projObj) {
dispatch(
addProj(C.ADD_PROJ, projObj)
);
}
});
export default connect (mapStateToProps, mapDispatchToProps)(projList);
export default projList;
My actions file is
import C from './constants'
export const addProj = ({title, endDate}) => {
return ({
type:C.ADD_PROJ,
payload: {
title, endDate
}
})
}
And my store file is :
import C from '../constants';
import { combineReducers } from 'redux';
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
export const addProjToList = (state=[], action) => {
switch (action.type) {
case C.ADD_PROJ :
return [
...state,
action.payload
]
default : return state
}
}
const appReducer = combineReducers({
addProjToList
});
export default (initialState={projList: []}) => {
return applyMiddleware(thunk)(createStore)(appReducer, initialState);
}
any help would be greatly appreciated. Thanks!
I am building a simple app to retrieve some recipes from an API URL.
I am reading the documentation of Thunk to implement it but I cannot understand how to set the async get request.
What is strange is that if I console.log the action passed into the reducer it definitely retrieves the correct object (a list of recipes for shredded chicken).
When I pass the action onto the the reducer, instead, it throws the error:
"Unhandled Rejection (Error): Given action "FETCH_RECIPES", reducer "recipes" returned undefined. To ignore an action, you must explicitly return the previous state. If you want this reducer to hold no value, you can return null instead of undefined."
Is there any error in my action creator? is the API call properly done?
Store
import React from 'react';
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import SearchBar from './components/App';
import thunk from 'redux-thunk';
import 'bootstrap/dist/css/bootstrap.css';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
render(
<Provider store={store}>
<SearchBar />
</Provider>,
document.getElementById('root')
)
Component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import './App.css';
import { Button } from 'reactstrap';
import { Form } from 'reactstrap';
import { bindActionCreators } from 'redux';
import {fetchRecipe } from '../actions';
class SearchBar extends Component {
constructor(props) {
super(props)
this.state = { term: ''};
this.typeRecipe = this.typeRecipe.bind(this)
this.onFormSubmit = this.onFormSubmit.bind(this);
}
onFormSubmit(e) {
e.preventDefault()
this.props.fetchRecipe(this.state.term)
}
typeRecipe(e) {
this.setState({term: e.target.value});
}
render() {
return (
<div className="SearchBar">
<Form onSubmit={this.onFormSubmit}>
<input type='text'
value={this.state.term}
placeholder='ciao'
onChange={this.typeRecipe}
/>
<br/>
<Button id='ciao' className='btn-success'>Submit</Button>
</Form>
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchRecipe }, dispatch);
}
export default connect(null, mapDispatchToProps)(SearchBar);
Action creator
import axios from 'axios';
export const FETCH_RECIPES = 'FETCH_RECIPES';
const API_KEY = 'xxx';//not the real one.
export function fetchRecipe() {
const request = axios.get(`http://food2fork.com/api/search?key=${API_KEY}&q=shredded%20chicken`);
return (dispatch) => {
request.then(({data}) =>{
dispatch({ type: FETCH_RECIPES, payload: data})
})
}
}
reducer
import { FETCH_RECIPES } from '../actions';
export default function (state = {}, action) {
switch(action.type) {
case FETCH_RECIPES:
const newState = action.payload.data;
return newState;
default:
return state
}
}
combineReducer (index)
import recipeReducer from '../reducers/recipes_reducer';
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
recipes: recipeReducer
});
export default rootReducer;
Mistake is in the reducer return statement.
export default function (state = {}, action) {
switch(action.type) {
case FETCH_RECIPES:
const newState = {...state, data : action.payload.data};
return newState;
default:
return state
}
}
Here we are adding data key to the reducer state, to access this you can use do this in you container :
export default connect((state)=>{
var mapStateToProps = {};
if(state.recipes.data) {
mapStateToProps['recipes'] = state.recipes.data
}
return mapStateToProps;
}, mapDispatchToProps)(SearchBar);
and recipes data will be available as this.props.recipes.
I'm doing a simple redux / react todo app. I can't get the todo items to show up. I'm able to console.log the data, but can't get it to appear. What am I doing wrong?
I separated the files, here is my app.js:
import React, { Component } from 'react';
import Todos from './todos';
import TodoList from "./todo_list";
export default class App extends Component {
render() {
return (
<div>
<Todos />
<TodoList/>
</div>
);
}
}
Here is the container Todos:
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { addTodo } from '../actions/index';
class Todos extends Component {
constructor(props) {
super(props);
this.state = {text: ''};
}
addTodo(e) {
e.preventDefault();
this.props.addTodo(this.state.text);
this.setState({
text: ''
});
}
updateValue(e) {
this.setState({text: e.target.value})
}
render() {
return (
<div>
<form onSubmit={(e) => this.addTodo(e)}>
<input
placeholder="Add Todo"
value={this.state.text}
onChange={(e) => {
this.updateValue(e)
}}
/>
<button type="submit">Add Todo</button>
</form>
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({addTodo}, dispatch);
}
export default connect(null, mapDispatchToProps)(Todos);
Here is the TodoList:
import React, {Component} from 'react';
import {connect} from 'react-redux';
class TodoList extends Component {
render() {
return (
<ul>
{ this.props.todo.map((tod) => {
return <li key={tod.message}>{ tod.message }</li>
})}
</ul>
);
}
}
function mapStateToProps({ todo }) {
console.log({ todo });
return { todo };
}
export default connect(mapStateToProps)(TodoList);
Reducer:
import { ADD_TODO } from '../actions/types';
export default function(state=[], action) {
switch(action.type) {
case ADD_TODO:
return [ action.payload.message, ...state ]
}
return state;
}
And action
import { ADD_TODO } from './types';
const uid = () => Math.random().toString(34).slice(2);
export function addTodo(message) {
const action = {
id: uid(),
message: message
};
return {
type: ADD_TODO,
payload: action
};
}
This is what I get from the console.log({todo});
Here is my reducers/index:
import { combineReducers } from 'redux';
import TodosReducer from './reducer_addTodo';
const rootReducer = combineReducers({
todo: TodosReducer
});
export default rootReducer;
It's because there's a disconnect between your TodoList and reducer. TodoList, when mapping, expects each todo to have a message prop, but your reducer, when returning next state, only includes the message in the state array, not an object with the message property:
case ADD_TODO:
return [ action.payload.message, ...state ]
Instead, do not just put the message string in the next state's array, put in the whole object:
case ADD_TODO:
return [ action.payload, ...state ]
Now every single element in the todo array will be an object and have a message and id property. Also, try using an always unique expression for key -- it really shouldn't be the todo message, nor the id you supplied because it's using Math.random which both have a possibility of keys being the same.