Condition on checkbox in React with Redux - reactjs

This is surely very simple but I dont understand how it works. I try to bind checkbox with state and with state display different string. It is in React with Redux. The code below (bold font)
container:
class DropingList extends Component {
**conditionHandler() {
if(this.props.pet === 'cat'){
return "YEAH!!!"
}else {return null;}**
}
render() {
return (
<div>
<AddHimHer
click={this.props.onAddMan}
/>
{ this.props.pers.map(per =>(
<NewPerson
key={per.id}
click={() => this.props.onManDown(per.id)}
name={per.name}
age={per.age}
**animal={this.conditionHandler(this.props.pet)}**
/>
))
}
</div>
)
}
}
const mapStateToProps = state => {
return {
pers: state.persons
}
}
const mapDispatchToProps = dispatch => {
return {
onAddMan: (name,age,**pet**) => dispatch({type:actionTypes.ADD_MAN, data: {nam: name, ag: age, **superp: pet**}}),
onManDown: (id) => dispatch({type:actionTypes.MAN_DOWN, Id: id})
}
}
export default connect(mapStateToProps,mapDispatchToProps)(DropingList);
component:
const NewPerson = (props) => (
<div onClick={props.click}>
<h1>Is {props.name} a SUPERHERO? ? ???</h1>
<h2>He is {props.age} years old</h2>
**<h1>{props.animal}</h1>**
</div>
);
export default NewPerson;
reducer:
const initState = {
persons: []
}
const personReducer = (state = initState,action) => {
switch (action.type) {
case actionTypes.ADD_MAN:
const newMan = {
id: Math.random(),
name: action.data.nam,
age: action.data.ag,
**pet: action.data.superp**
};
return {
...state,
persons: state.persons.concat(newMan)
};
case actionTypes.MAN_DOWN:
return {
...state,
persons: state.persons.filter(person => person.id !== action.Id)
};
}
return state;
};
export default personReducer;
I am still newbe in React and Redux. I think I have ommited something.
Could you tell me whats wrong with my code?

Issue is pet is the part of the object (each object of the array), not a separate prop so you need to use per.pet in map callback function, like this:
{this.props.pers.map(per =>(
<NewPerson
key={per.id}
click={() => this.props.onManDown(per.id)}
name={per.name}
age={per.age}
animal={this.conditionHandler(per.pet)} // here
/>
))}
Now you are passing the pet value to function conditionHandler, so no need to use this.props.pet inside that directly use pet, like this:
conditionHandler(pet) {
if (pet === 'cat') {
return "YEAH!!!"
} else {
return null;
}
}

Related

Sorting Data in Reducer Actions

I am trying to create a sort button which when clicked will sort me menu cards alphabetically. My question is how should I have the sort function coded in the Reducer and Actions? I added pseudo-code for sorting in the Reducer as well. When I click the button I am getting "(TypeError): state.slice is not a function".
Edit:
Added my button component and main Container.
Actions:
export const sortMenus = () => {
return dispatch => {
dispatch({ type: "LOADING_MENUS" });
fetch(`/api/menus`)
.then(res => res.json())
.then(responseJSON => {
dispatch({ type: "SORT_MENUS", cards: responseJSON });
});
};
};
Reducer:
export default function MenusReducer(
state = {
cards: [],
loading: false
},
action
) {
switch (action.type) {
case "LOADING_MENUS":
return {
...state
};
case "ADD_MENUS":
return {
...state,
cards: action.cards
};
case "SORT_MENUS":
return state.slice().sort(function(menu1, menu2) {
if (menu1.name < menu2.name) return -1;
if (menu1.name < menu2.name) return 1;
return 0;
});
default:
return state;
}
}
Button Component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { sortMenus } from ".././actions/dataActions";
import Row from "react-bootstrap/Row";
import Container from "react-bootstrap/Container";
class SortButton extends Component {
constructor() {
super();
this.state = { menus: [] };
}
handleMenuSort = e => {
this.props.sortMenus()
};
render() {
return (
<Container>
<Row>
<div>
<button id="sort-button" title="Sort Menus" onClick= {this.handleMenuSort}>Sort Menus</button>
</div>
</Row>
</Container>
)
}
}
const mapStateToProps = state => {
return {
menus: state.menus
}
};
const mapDispatchToProps = dispatch => {
return {
sortMenus: params => dispatch(sortMenus(params)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(SortButton)
Container:
class MainContainer extends Component {
displayCards = () => {
switch(this.props.path) {
case "menus":
return (this.props.menus.cards.map(card => (
<NavLink style={{ color: "black" }} to={`/menus/${card.id}`} key={card.id}><MenuCard view={this.props.displayObject} info={card} /></NavLink>
)));
default:
return (<div>Empty</div>)
}
};
render() {
return (
<CardColumns>
{this.displayCards()}
</CardColumns>
)
}
}
const mapStateToProps = state => {
return {
menus: state.menus
}
};
const mapDispatchToProps = dispatch => {
return {
displayObject: (id, category, type) => dispatch(displayObject(id, category, type)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(MainContainer)
Your state is an object, not an array. You likely mean to sort the stored cards array.
state.cards.slice(... instead of state.slice(...
case "SORT_MENUS":
return state.cards.slice().sort(function(menu1, menu2) {
if (menu1.name < menu2.name) return -1;
if (menu1.name < menu2.name) return 1;
return 0;
});
Side note: You may also want to clear/set your loading state upon successful data fetching. ;)
EDIT
You are mapping undefined state within mapStateToProps, then mapping over it in the component. Change mapStateToProps to access the correct defined property.
const mapStateToProps = state => ({
cards: state.cards,
});
Then you can iterate over the new cards prop.
case "menus":
return (this.props.cards.map(card => (
<NavLink
style={{ color: "black" }}
to={`/menus/${card.id}`}
key={card.id}
>
<MenuCard view={this.props.displayObject} info={card} />
</NavLink>
)));
You can simply store the fetched menu in application state.
You can have standalone action say SORT_MENU_BY_ALPHABET.
You can simply dispatch this action on button handler as well as on Ajax success. this dispatch may not have any payload associated.
hope it helps.
in reducer you defined state as object and you're trying to do array operation on it. state.slice().
slice is a function available for arrays. so its throwing error.
you should be doing
state.cards.slice().sort((a,b)=> a-b)

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.

Where does the reducer get the state from?

I am trying to understand redux with the help of an online todo application resource.
However, I can't seem to figure out where does the 'todos' reducer get the initial state from ?
I've consoled the state but can't seem to wrap my head around it ?
After the initial render, state is consoled 3 times as,
[ ]
[ ]
[ state object ]
Link: 'https://codepen.io/iamrkcheers/pen/rNNoBvB'
Any help is appreciated.
Thank You.
// --------- actions start ----------
const ADD_TODO = "ADD_TODO";
const TOGGLE_TODO = "TOGGLE_TODO";
const SET_VISIBILITY_FILTER = "SET_VISIBILITY_FILTER";
const VisibilityFilters = {
SHOW_ALL: "SHOW_ALL",
SHOW_COMPLETED: "SHOW_COMPLETED",
SHOW_ACTIVE: "SHOW_ACTIVE"
};
let nextTodoId = 3;
function addTodo(text) {
return {
type: ADD_TODO,
id: nextTodoId++,
text
}
}
function toggleTodo(id) {
return {
type: TOGGLE_TODO,
id
}
}
function setVisibilityFilter(filter) {
return {
type: SET_VISIBILITY_FILTER,
filter
}
}
// --------- actions end ----------
// --------- reducers start ----------
function todos(state = [], action) {
console.log('state is:',state);
switch (action.type) {
case ADD_TODO: {
return [...state, {
text: action.text,
completed: false,
id: action.id
}];
}
case TOGGLE_TODO: {
return state.map((todo, id) => {
if (id === action.id) {
return Object.assign({}, todo, {
completed: !todo.completed
});
}
return todo;
});
}
default: {
return state;
}
}
}
function visibilityFilter(state = VisibilityFilters.SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER: {
return action.filter;
}
default: {
return state
}
}
}
const todoApp = Redux.combineReducers({
visibilityFilter,
todos
});
// --------- reducers end ----------
// --------- components start ----------
const App = () => {
const getDate = date => new Date(date);
const days = ["Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"];
return (
<div className="block">
<div className="info-date">
<div className="date">{ getDate(Date.now()).toLocaleDateString("ru") }</div>
<div className="day">{ days[getDate(Date.now()).getDay()] }</div>
</div>
<AddTodo />
<Footer />
<VisibleTodoList />
</div>
);
};
const Footer = () => {
return (
<div className="filters">
<FilterLink filter="SHOW_ALL">Все задачи</FilterLink>
<FilterLink filter="SHOW_ACTIVE">Активные</FilterLink>
<FilterLink filter="SHOW_COMPLETED">Завершенные</FilterLink>
</div>
);
};
const Link = ({ active, children, onClick }) => {
if (active) {
return <span className="filter-item non-active">{ children }</span>
}
return (
<a className="filter-item" href="#" onClick = { event => {
event.preventDefault();
onClick();
} }>{ children }</a>
);
};
const Todo = ({ onClick, completed, text }) => {
const styles = {
textDecoration: completed ? "line-through" : "none"
};
return (
<li onClick = { onClick } style = { styles }>
<a>{ text }</a>
</li>
);
};
const TodoList = ({ todos, onTodoClick }) => {
return (
<div className="list">
<ul>
{
todos.map(todo => <Todo
key = { todo.id } { ...todo }
onClick = { () => onTodoClick(todo.id) } />)
}
</ul>
</div>
);
};
// --------- components end ----------
// --------- containers start ----------
let AddTodo = ({ dispatch }) => {
let input;
return (
<div>
<form className="addForm" onSubmit = { event => {
event.preventDefault();
if (!input.value.trim()) {
return;
}
dispatch(addTodo(input.value));
input.value = "";
} }>
<input type="text" placeholder="Что нужно сделать?" ref = { node => input = node }/>
<button type="submit" className="btn"></button>
</form>
</div>
);
};
AddTodo = ReactRedux.connect()(AddTodo);
var mapStateToProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibilityFilter
};
};
var mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch(setVisibilityFilter(ownProps.filter));
}
};
};
const FilterLink = ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(Link);
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case "SHOW_ALL": {
return todos;
}
case "SHOW_COMPLETED": {
return todos.filter(todo => todo.completed);
}
case "SHOW_ACTIVE": {
return todos.filter(todo => !todo.completed);
}
default: {
return todos;
}
}
};
var mapStateToProps = state => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
};
var mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id));
}
};
};
const VisibleTodoList = ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(TodoList);
// --------- containers end ----------
// --------- application start ----------
const initialState = {
visibilityFilter: "SHOW_ALL",
todos: [
{
id: 0,
text: "Изучить React",
completed: true
},
{
id: 1,
text: "Изучить Redux",
completed: true
},
{
id: 2,
text: "Написать приложение \"Список задач\"",
completed: false
}
]
};
let store = Redux.createStore(todoApp, initialState);
ReactDOM.render(
<ReactRedux.Provider store = { store }>
<App />
</ReactRedux.Provider>,
document.querySelector("#root")
);
// --------- application end ----------
You are defining the initial state right here :
function todos(state = [], action) {
Generally, while defining reducers, we also define initialState(state = [] in your case) , which is the state that goes into the reducer till we populate it with data (from an external source like api, or user input).
You can read more on initial state here : https://redux.js.org/recipes/structuring-reducers/initializing-state#initializing-state
there are two ways where you can define initial state;
the first one is in your reducer where you did function
todos(state = [], action) and ,
the second is when you create the store, you can pass initial state as a second argument in your createStore function. In your case , you have a second argument when you create your store which is an array of three todos which you can see when you console log it. store = Redux.createStore(todoApp, initialState), here the reducer gets this initial state

Objects in array turn into undefined after using a particular reducer/action

I'm working with a classic to do list project to learn Redux and I'm having a strange issue.
Basically, I have a to-do list with checkboxes and when the user clicks on a checkbox, an action gets dispatched which should mark that object's completed property as true and the component should update.
However... When this action fires, the object which is supposed to be marked as complete successfully returns with all of it's properties but the rest of the todo list (the other objects in the array) get corrupted, losing all their properties and they turn into 'undefined' thus causing problems in the render.
I've tried to include all the code that I think is relevant but I think that I'm doing something wrong in my reducer, but I can't find seem to find the issue.
Todo List Component
class TodoList extends Component {
render(){
const {todos, showCompleted, searchTerm} = this.props;
const renderTodos = () => {
if (todos.length === 0) {
return (
<p className="container__message">Nothing to do.</p>
);
}
return TodoAPI.filterTodos(todos, showCompleted, searchTerm).map((todo) => {
return (
<Todo key={todo.id} {...todo}/>
);
});
};
return (
<div>
{renderTodos()}
</div>
);
}
}
export default connect((state) => {
return state;
})(TodoList);
Todo Component
class Todo extends Component {
render() {
const {id, text, completed, createdAt, completedAt, dispatch} = this.props;
const todoClass = completed
? 'todo todo-completed'
: 'todo';
const renderDate = () => {
let displayMessage = 'Created ';
let timestamp = createdAt;
if (completed) {
displayMessage = 'Completed ';
timestamp = completedAt;
}
return displayMessage + moment.unix(timestamp).format('MMM Do YYYY # h:mm a');
};
return (
<div className={todoClass}
onClick={event => dispatch(actions.toggleTodo(id)) }>
<input type="checkbox" checked={completed} readOnly/>
<div>
<p>{text}</p>
<p className="todo__subtext">{renderDate()}</p>
</div>
</div>
);
}
}
export default connect()(Todo);
Action
export const toggleTodo = (id) => {
return {
type: 'TOGGLE_TODO',
id: id
};
};
Reducer
export const todosReducer = (state = [], action) => {
switch (action.type) {
case 'TOGGLE_TODO':
return state.map((todo) => {
if (todo.id === action.id) {
let nextCompleted = !todo.completed;
return {
...todo,
completed: nextCompleted,
completedAt: todo.completed ? moment().unix() : 0
};
}
});
default:
return state;
}
};
Issue is you are not returning anything if the condition todo.id === action.id fail. With the map if you don't return anything, by default it will return undefined, Try this:
return state.map((todo) => {
if (todo.id === action.id) {
let nextCompleted = !todo.completed;
return {
...todo,
completed: nextCompleted,
completedAt: todo.completed ? moment().unix() : 0
};
}else{
return todo;
}
});
Check this:
a=[1,2,3,4,5,6];
b = a.map ( i => { if(i % 2 == 0) return i;})
console.log(b);

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