I connected a component to the store in a way that it's able to dispatch actions and receive state. The dispatch works and I can see the state change in the Redux dev tool. However, I'm unable to pass the state back to the component. What am I missing here?
Component
The toggle() and addTask() are successfully dispatched. However, the newTask doesn't receive the state.
class AddTask extends React.Component {
state = {
value: '',
};
setValue = value => this.setState({ value });
handleInput = () => !this.props.newTask ? 'hidden' : '';
handleToggle = () => this.props.toggle();
handleSubmit = (e) => {
e.preventDefault();
const title = this.state.value;
this.props.addTask(title);
this.props.toggle();
};
render() {
return (
<div className="add-task">
<div className="btn-add-task">
<Button
type="LINK"
label="Add a Task"
onClick={this.handleToggle}
/>
</div>
<form
className={`task-input ${this.handleInput()}`}
onSubmit={this.handleSubmit}
>
<InputField
placeholder="Task title"
value={value => this.setValue(value)}
/>
</form>
</div>
);
}
};
export default AddTask;
Container
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import AddTask from './TaskList/AddTask/AddTask';
import * as actionCreators from '../actions/taskActions';
const mapStateToProps = state => ({
newTask: state.newTask,
});
const mapDispatchToProps = dispatch => (
bindActionCreators(actionCreators, dispatch)
);
export default connect(
mapStateToProps,
mapDispatchToProps,
)(AddTask);
Reducer
import {
TOGGLE_TASK,
} from '../constants/actions';
const uiReducer = (state = {
newTask: false,
}, action) => {
switch (action.type) {
case TOGGLE_TASK:
return {
...state,
newTask: !state.newTask,
};
default:
return state;
}
};
export default uiReducer;
Store
const initialState = {
tasks: [
{
title: 'Title A', description: 'Description A', effort: '12 hrs', editing: 'false',
},
{
title: 'Title B', description: 'Description B', effort: '24 hrs', editing: 'false',
},
{
title: 'Title C', description: 'Description C', effort: '', editing: 'false',
},
],
ui: {
newTask: false,
},
};
Print
tasks: Array(4)
0: {title: "Title A", description: "Description A", effort: "12 hrs", editing: "false"}
1: {title: "Title B", description: "Description B", effort: "24 hrs", editing: "false"}
2: {title: "Title C", description: "Description C", effort: "", editing: "false"}
3: {id: "", title: "asdfasd", description: undefined}
length: 4
__proto__: Array(0)
ui:
newTask: false
Thanks!
Related
i am trying to build a react redux form using the connect() instead useSelector and useDispatch.
I managed to display the list of data and to reset the forms. But i didn't manage to send data. Here is the code:
Reducer.js
const initialState = {
tasks: {
name: "",
age: "",
job: "",
},
list: [
{
id: 0,
name: "Maillard",
age: 35,
job: "soldier",
},
],
};
export const toDoReducer = (state = initialState, action) => {
switch (action.type) {
case "name":
return {
...state,
tasks: {
name: action.payload,
},
};
case "age":
return {
...state,
tasks: {
age: action.payload,
},
};
case "job":
return {
...state,
tasks: {
job: action.payload,
},
};
case "clear":
return {
...state,
tasks: {
name: "",
age: "",
job: "",
},
};
case "add":
return {
...state,
tasks: {
...state.tasks,
id: state.list.length + 1,
},
};
default:
return {
...state,
};
}
};
export default toDoReducer;
import React from "react";
import { connect } from "react-redux";
import {
setName,
setAge,
setJob,
clearForm,
addForm,
} from "../../redux/action";
export const Form = (props) => {
return (
<div>
Name
<input value={props.list.name} onChange={(e) => { props.Name(e.target.value) }} />
Age
<input value={props.list.age} onChange={(e) => { props.Age(e.target.value) }} />
Profession
<input value={props.list.job} onChange={(e) => { props.Job(e.target.value) }} />
<div style={{ padding: 20 }}>
<button onClick={props.Clear}>Reset</button>
<button onClick={props.Add}>Envoyer</button>
</div>
</div>
);
};
const mapDispatchToProps = (dispatch) => {
return {
Name: () => {
dispatch({
type: "name",
setName,
});
},
Age: () => {
dispatch({ type: "age", setAge });
},
Job: () => {
dispatch({
type: "job",
setJob,
});
},
Clear: () => {
dispatch({ type: "clear", clearForm, });
},
Add: () => {
dispatch({)}
// My problem comes from the Add
<!-- begin snippet: js hide: false console: true babel: false -->
type: "add",
addForm
})
}
};
};
const mapStateToProps = (state) => ({
list: state.tasks,
});
export default connect(mapStateToProps, mapDispatchToProps)(Form);
// export default Form;
My problems comes from in mapDispatchToProps, i don't know what to do for the function Add
Your actions should be the ones defining the action type and you should call them in the mapDispatchToProps, not pass them as part of the action.
So your addForm should be something like
const addForm = () => ({
type:'add'
});
and in your mapDispatchToProps it should be like
Add: () => dispatch(addForm()),
But you have the same problem with all your dispatching
for example the Name should be
action
const setName = (payload) => ({
type:'name',
payload
});
mapDispatchToProps
Name: (nameValue) => {
dispatch(setName(nameValue));
},
I'm working on a todo list in my current project.
I can display the todo list but when I click the checkbox to mark a task as complete I get this TypeError:
I've tried to use Google and Stack to find an answer but still can't figure out what it is I'm doing wrong. Why is toggleComplete not a function?
Reducer / todosOne.js
import { createSlice } from '#reduxjs/toolkit'
export const todosOne = createSlice({
name: 'todos',
initialState: [
{ id: 1, text: 'This is a todo item', complete: false },
{ id: 2, text: 'This is a todo item', complete: false },
{ id: 3, text: 'This is a todo item', complete: false },
{ id: 4, text: 'This is a todo item', complete: false },
{ id: 5, text: 'This is a todo item', complete: false },
],
toggleComplete: (store, action) => {
const checkedItem = store.items.find(item => item.id === action.payload)
if (checkedItem) {
checkedItem.complete = !checkedItem.complete
}
}
})
Component / TodoListOne.js
import React from 'react'
import styled from 'styled-components'
import { useSelector, useDispatch } from 'react-redux'
import { todosOne } from '../Reducers/todosOne'
export const TodoListOne = () => {
const dispatch = useDispatch();
const items = useSelector(store => store.todos);
const onChecked = complete => {
dispatch(todosOne.actions.toggleComplete(complete))
}
return (
<>
{items.map(todos => (
<TodoContainer key={todos.id}>
<List>
<label>
<input type="checkbox"
checked={todos.complete}
onChange={() => onChecked(todos.id)}
/>
</label>
</List>
<TodoText>{todos.text}</TodoText>
</TodoContainer>
))}
</>
)
}
It should be
export const todosOne = createSlice({
name: 'todos',
initialState: [
{ id: 1, text: 'This is a todo item', complete: false },
{ id: 2, text: 'This is a todo item', complete: false },
{ id: 3, text: 'This is a todo item', complete: false },
{ id: 4, text: 'This is a todo item', complete: false },
{ id: 5, text: 'This is a todo item', complete: false },
],
// here!
reducers: {
toggleComplete: (store, action) => {
const checkedItem = store.items.find(item => item.id === action.payload)
if (checkedItem) {
checkedItem.complete = !checkedItem.complete
}
}
// here!
}
})
I am trying to make an application that allows the user to select 'Add to cart' within my DetailsComponent.js page. I am currently updating an array within my store named 'cart' with ids that correspond to products within a products array within the store. I have checked the debugger and the product id is correctly being added to the cart array.
I am using the following code to show products on my cart page that match the product ids in my cart but my cart is currently showing nothing.
CartComponent.js
import React, { Component } from 'react';
import { FlatList, View, Text, Alert } from 'react-native';
import { ListItem } from 'react-native-elements';
import { connect } from 'react-redux';
import { baseUrl } from '../shared/baseUrl';
import { Loading } from './LoadingComponent';
import { removeCart } from '../redux/ActionCreators';
const mapStateToProps = state => {
return {
products: state.products,
cart: state.cart
}
}
const mapDispatchToProps = dispatch => ({
removeCart: (id) => dispatch(removeCart(id))
});
class CartScreen extends Component {
render() {
const renderMenuItem = ({item, index}) => {
return(
<ListItem
key={index}
bottomDivider
>
<ListItem.Content>
<ListItem.Title>
{item.name}
</ListItem.Title>
<ListItem.Subtitle>
{item.quantity} chargers: ${item.price}
</ListItem.Subtitle>
</ListItem.Content>
</ListItem>
);
}
if (this.props.cart.isLoading) {
return(
<Loading />
)
}
else if (this.props.cart.errMess) {
return(
<Text>{this.props.cart.errMess}</Text>
)
}
else {
return(
<View>
<Text>
Cart
</Text>
<FlatList
data={this.props.products.products.filter(product => this.props.cart.cart.some(el => el === product.id))}
renderItem={renderMenuItem}
keyExtractor={item => item.id.toString()}
/>
</View>
);
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CartScreen);
I have included my other files for more information.
I have different DropDownPicker values that correspond to the product IDs in my redux store. These items in the store have quantity and price values to be used in the CartComponent.js.
DetailsComponent.js
import React, { Component } from 'react';
import { Text, View, Image, Button, FlatList, StyleSheet, ScrollView} from 'react-native';
import DropDownPicker from 'react-native-dropdown-picker';
import { baseUrl } from '../shared/baseUrl';
import { connect } from 'react-redux';
import { Loading } from './LoadingComponent';
import { postCart } from '../redux/ActionCreators';
const mapStateToProps = state => {
return{
chargers: state.chargers,
utensils: state.utensils,
orders: state.orders,
products: state.products
}
}
const mapDispatchToProps = dispatch => ({
postCart: (id) => dispatch(postCart(id))
})
class DetailsScreen extends Component {
constructor(props) {
super();
this.state = {
itemId: '',
orderAmount: '',
orderPrice: ''
}
}
addToCart(id) {
this.props.postCart(id);
}
render() {
const categoryName = this.props.route.params.categoryName;
const productId = this.props.route.params.menuId;
const item = this.props[categoryName][categoryName][productId];
if (categoryName === "chargers") {
if (productId === 0) {
var amounts = [
{label: '50', value: '1'},
{label: '100', value: '2'},
{label: '150', value: '3'},
{label: '200', value: '4'},
{label: '250', value: '5'},
{label: '300', value: '6'},
{label: '350', value: '7'},
{label: '400', value: '8'},
{label: '450', value: '9'},
{label: '500', value: '10'},
{label: '550', value: '11'},
{label: '600', value: '12'},
];
}
else if (productId === 1) {
var amounts = [
{label: '50', value: '14'},
{label: '100', value: '15'},
{label: '150', value: '16'},
{label: '200', value: '17'},
{label: '250', value: '18'},
{label: '300', value: '19'},
{label: '350', value: '20'},
{label: '400', value: '21'},
{label: '450', value: '22'},
{label: '500', value: '23'},
{label: '550', value: '24'},
{label: '600', value: '25'},
];
}
else if (productId === 2) {
var amounts = [
{label: '50', value: '27'},
{label: '100', value: '28'},
{label: '150', value: '29'},
{label: '200', value: '30'},
{label: '250', value: '31'},
{label: '300', value: '32'},
{label: '350', value: '33'},
{label: '400', value: '34'},
{label: '450', value: '35'},
{label: '500', value: '36'},
{label: '550', value: '37'},
{label: '600', value: '38'},
];
}
}
else if (categoryName === "utensils") {
if (productId === 0) {
var amounts = [
{label: '1', value: '40'},
{label: '2', value: '41'},
{label: '3', value: '42'},
{label: '4', value: '43'},
{label: '5', value: '44'},
];
}
else if (productId === 1) {
var amounts = [
{label: '1', value: '46'},
{label: '2', value: '47'},
{label: '3', value: '48'},
{label: '4', value: '49'},
{label: '5', value: '50'},
];
}
}
if (this.props[categoryName].isLoading) {
return(
<Loading />
)
}
else if (this.props[categoryName].errMess) {
return(
<Text>
{this.props[categoryName][categoryName].errMess}
</Text>
)
}
else {
return(
<ScrollView>
<Text style={styles.title}>
{item.name}
</Text>
<Text>
{item.category}
</Text>
<View style={{flex: 1, flexDirection: 'row'}}>
<Image
style={styles.image}
source={{uri: baseUrl + item.image}}
/>
<DropDownPicker
items={amounts}
defaultNull
placeholder="Select amount"
containerStyle={{height: 40, width: 100}}
itemStyle={{
justifyContent: 'flex-start'
}}
onChangeItem={item => this.setState({
itemId: this.props.products.products[item.value].id,
orderAmount: this.props.products.products[item.value].quantity,
orderPrice: this.props.products.products[item.value].price
})}
/>
</View>
<Text>
{item.description}
</Text>
<Text>
Your order is {this.state.orderAmount} {this.props[categoryName][categoryName][productId].name} chargers for ${this.state.orderPrice}
</Text>
<Button
title='Add to Cart'
color="#f194ff"
onPress={() => this.addToCart(this.state.itemId)}
/>
</ScrollView>
)
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
image: {
resizeMode: "contain",
height: 200,
width: 200
},
title: {
fontSize: 32,
},
});
export default connect(mapStateToProps, mapDispatchToProps)(DetailsScreen);
The following code samples are my redux files. I have simplified them with only the cart information for readability.
ActionCreators.js
export const fetchCart = () => (dispatch) => {
dispatch(cartLoading());
return fetch(baseUrl + 'cart')
.then(response => {
if (response.ok) {
return response;
} else {
var error = new Error('Error ' + response.status + ': ' + response.statusText);
error.response = response;
throw error;
}
},
error => {
var errmess = new Error(error.message);
throw errmess;
})
.then(response => response.json())
.then(cart => dispatch(addCart(cart)))
.catch(error => dispatch(cartFailed(error.message)));
};
export const cartLoading = () => ({
type: ActionTypes.CART_LOADING
});
export const cartFailed = (errmess) => ({
type: ActionTypes.CART_FAILED,
payload: errmess
});
export const postCart = (id) => (dispatch) => {
const newCart = {
id: id
};
setTimeout(() => {
dispatch(addToCart(newCart));
}, 2000);
};
export const addToCart = (cart) => ({
type: ActionTypes.ADD_TO_CART,
payload: cart
});
export const addCart = (id) => ({
type: ActionTypes.ADD_CART,
payload: id
});
export const removeCart = (id) => ({
type: ActionTypes.REMOVE_CART,
payload: id
});
ActionTypes.js
export const POST_CART = 'POST_CART';
export const ADD_TO_CART = 'ADD_TO_CART';
export const ADD_CART = 'ADD_CART';
export const REMOVE_CART = 'REMOVE_CART';
export const CART_LOADING = 'CART_LOADING';
export const CART_FAILED = 'CART_FAILED';
cart.js
import * as ActionTypes from './ActionTypes';
export const cart = (
state = {
isLoading: true,
errMess: null,
cart:[]
},
action) => {
switch (action.type) {
case ActionTypes.ADD_CART:
return {...state, isLoading: false, errMess: null, cart: action.payload};
case ActionTypes.CART_LOADING:
return {...state, isLoading: true, errMess: null, cart: []};
case ActionTypes.CART_FAILED:
return {...state, isLoading: false, errMess: action.payload};
case ActionTypes.ADD_TO_CART:
var newCart = action.payload;
return {...state, cart: state.cart.concat(newCart) };
default:
return state;
}
};
The following is my db.json file. I have input one item into the cart array for testing but it also does not show up in the cart. Have also simplified this file to only show a few products for readability.
"products": [
{
"id": 0,
"name": "Ezywhip Pro",
"category": "chargers",
"label": "",
"featured": false,
"description": "Ezywhip Pro Cream Chargers, Made by MOSA",
"image": "images/ezywhip.png",
"quantity": 0,
"price": 0
},
{
"id": 1,
"name": "Ezywhip Pro",
"category": "ezy",
"label": "",
"featured": false,
"description": "Ezywhip Pro Cream Chargers, Made by MOSA",
"image": "images/ezywhip.png",
"quantity": 50,
"price": 40
},
{
"id": 2,
"name": "Ezywhip Pro",
"category": "ezy",
"label": "",
"featured": false,
"description": "Ezywhip Pro Cream Chargers, Made by MOSA",
"image": "images/ezywhip.png",
"quantity": 100,
"price": 70
},
{
"id": 3,
"name": "Ezywhip Pro",
"category": "ezy",
"label": "",
"featured": false,
"description": "Ezywhip Pro Cream Chargers, Made by MOSA",
"image": "images/ezywhip.png",
"quantity": 150,
"price": 110
}
],
"cart": [
{
"id": 1
}
]
}
If anyone could explain what I'm doing wrong it would be greatly appreciated.
I got it working by changing the following:
export const addToCart = (id) => ({
type: ActionTypes.ADD_TO_CART,
payload: id
});
Not sure if this was causing any issues but I changed it to id for safe measure.
This is what was causing the main issue, I needed to use el.id to specify that's what I was comparing with in the data.
<FlatList
data={this.props.products.products.filter(product => this.props.carts.carts.some(el => el.id === product.id))}
renderItem={renderMenuItem}
keyExtractor={item => item.id.toString()}
/>
In the following code, I was curious as to how you would set the value of the following TextField.
For this example, how do I set the TextField to the selected item in the Dropdown?
If the user selects "TOP LEVEL" in the Dropdown, then I want to populate the TextField to be "TOP LEVEL". The Dropdown is called ChildComponent
import * as React from "react";
import ChildComponent from './Operations/ChildComponent';
import { DropdownMenuItemType, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { TextField} from 'office-ui-fabric-react/lib/TextField';
export interface ParentComponentState {
selectedItem?: { key: string | number | undefined };
value: {key: string};
}
export default class ParentComponent extends React.Component<{}, ParentComponentState> {
constructor(props, context) {
super(props, context);
}
public state: ParentComponentState = {
selectedItem: undefined,
value: undefined,
};
render(){
const { selectedItem } = this.state;
const options: IDropdownOption[] = [
{ key: 'blank', text: '' },
{ key: 'topLevelMake', text: 'Parents', itemType: DropdownMenuItemType.Header },
{ key: 'topLevel', text: 'TOP LEVEL' },
{ key: 'make', text: 'MAKE ITEM' },
{ key: 'divider_1', text: '-', itemType: DropdownMenuItemType.Divider },
{ key: 'Purchased', text: 'Purchases', itemType: DropdownMenuItemType.Header },
{ key: 'rawMaterial', text: 'RAW MATERIAL' },
{ key: 'buyItem', text: 'BUY ITEM' },
];
return(
<div>
<ChildComponent
options={options}
selectedKey={selectedItem ? selectedItem.key : undefined}
onChange={this._onChange}
/>
<TextField
name="textTest"
label={"Test"}
/>
</div>
);
}
private _onChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
this.setState({ selectedItem: item });
let opValue = item.text;
console.log(event);
console.log(opValue);
};
}
After inserting Muhammad's logic, here is the error I am getting. Do I need to add an onChange event for the TextField? and then put "this.state.selectedItem" in the handleChange event? Do I need to make a new child component and have the TextField rollup to ParentComponent?
You just need to assign that state in the value prop for the textField as you have the selectedItem in your state
<TextFieid
label={"Test"}
styles={{ root: { width: 300 } }}
value={this.state.selectedItem}
/>
import * as React from "react";
import ChildComponent from './Operations/ChildComponent';
import { DropdownMenuItemType, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { TextField} from 'office-ui-fabric-react/lib/TextField';
export interface ParentComponentState {
selectedItem?: { key: string | number | undefined };
value?;
}
export default class ParentComponent extends React.Component{
constructor(props) {
super(props);
this.state = {
value: '',
};
}
public state: ParentComponentState = {
selectedItem: undefined,
};
handleChange = (event) => {
this.setState({
value: event.target.value,
})
};
render(){
const { selectedItem } = this.state;
const options: IDropdownOption[] = [
{ key: 'blank', text: '' },
{ key: 'topLevelMake', text: 'Parents', itemType: DropdownMenuItemType.Header },
{ key: 'topLevel', text: 'TOP LEVEL' },
{ key: 'make', text: 'MAKE ITEM' },
{ key: 'divider_1', text: '-', itemType: DropdownMenuItemType.Divider },
{ key: 'Purchased', text: 'Purchases', itemType: DropdownMenuItemType.Header },
{ key: 'rawMaterial', text: 'RAW MATERIAL' },
{ key: 'buyItem', text: 'BUY ITEM' },
];
return(
<div>
<ChildComponent
options={options}
selectedKey={selectedItem ? selectedItem.key : undefined}
onChange={this._onChange}
/>
<TextField
name="textTest"
label={"Test"}
onChange={this.handleChange}
value={this.state.value}
/>
</div>
);
}
private _onChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
this.setState({ selectedItem: item });
this.setState({value: item.text})
let opValue = item.text;
console.log(event);
console.log(opValue);
};
}
So no matter what I do I can't get the component to refresh when I add a item to an array in the redux store.
What I use in my reducer to add to the redux state:
case ADD_NOTE_META:
return [
...state,
action.note,
];
The connector:
import { connect } from 'react-redux';
import NoteSection from './NoteSection.component';
const mapStateToProps = state => ({
notes: state.NotesMeta,
});
const mapDispatchToProps = () => ({});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(NoteSection);
The component:
import React from 'react';
import PropTypes from 'prop-types';
import NoteSelectorContainer from './noteselector/NoteSelector.connector';
import DeleteConfirmationMessage from './deletenoteconfirmationmessage/DeleteConfirmationMessage.connector';
function NoteSection(props) {
const { notes } = props;
return (
<div id="divSelectNoteContainer">
{notes.map(item => (
<NoteSelectorContainer
note={item}
key={item.id}
/>
))}
<DeleteConfirmationMessage />
</div>
);
}
NoteSection.defaultProps = {
notes: [],
};
NoteSection.propTypes = {
notes: PropTypes.array,
};
export default NoteSection;
The state in redux is structured like:
{
NotesMeta: [
{
id: '5b6cd6c49a46d',
title: 'folder note',
tags: [
'test'
],
parentid: '5b6cd6c49a48d'
},
{
id: '5b6cd6c496ad2',
title: 'test note',
tags: [],
parentid: null
},
]
}
Output of console.log(notes) before add new note is run:
0: {id: "5b6cd6c49a46d", title: "folder note", tags: Array(1), parentid: "5b6cd6c49a48d"}
1: {id: "5b6cd6c496ad2", title: "test note", tags: Array(0), parentid: null}
After:
0: {id: "5b6cd6c49a46d", title: "folder note", tags: Array(1), parentid: "5b6cd6c49a48d"}
1: {id: "5b6cd6c496ad2", title: "test note", tags: Array(0), parentid: null}
2: {id: "5bb48aaae94c1", title: "New Note Title", tags: Array(0)}
I can see that the new note is added in both the redux store and the Notesection props however a new NoteSelectorContainer is never created.