i am learning redux here. I got problems when i submit my input. I have 2 text input and i want to store it in an array, but when i got the error. it seems like the app adding a new one undefined data, so i can't load the property of undefined. But when i used one params in actions, It works, i don't know why. It is first time i am using redux, i hope you can help me, thank you
actions.js
import { ADD_PERSON, DEL_PERSON} from '../constants';
export const addPerson = (person, age) => {
return {
type: ADD_PERSON,
person,
age
}
}
export const delPerson = (person, age) => {
return {
type: DEL_PERSON,
person,
age
}
}
my reducers
import { ADD_PERSON, DEL_PERSON} from '../constants';
const initState = {people: [{ name: 'Wahyu', age: '18' }]}
export default function peopleReducer(state = initState, action){
switch (action.type) {
case ADD_PERSON:
return {
people: [
...state.people,
action.person,
action.age,
],
};
case DEL_PERSON:
return {
people: state.people.filter(p => p.name !== action.person.name),
};
default:
return state;
}
}
components file
state = {
name: '',
age: '',
}
addPerson = () => {
if (this.state.name === '') return;
this.props.dispatchAddPerson({
name: this.state.name,
age: this.state.age,
});
}
deletePerson = (person) => {
this.props.dispatchdeletePerson(person)
}
render() {
console.log(this.props.people)
return (
<View>
<Text style={styles.title}>People</Text>
<TextInput
onChangeText={(name) => this.setState({name})}
style={styles.input}
value={this.state.name}
placeholder="Name"
/>
<TextInput
onChangeText={(age) => this.setState({age})}
style={styles.input}
value={this.state.age}
placeholder="Age"
/>
<TouchableHighlight
underlayColor="#ffa012"
style={styles.button}
onPress={this.addPerson}
>
<Text style={styles.buttonText}>Add Person</Text>
</TouchableHighlight>
{
this.props.people.map((person, index) => (
<View key={index} style={styles.person}>
<Text>Name: {person.name}</Text>
<Text>Age: {person.age} </Text>
<Text onPress={() => this.deletePerson(person)}>Delete Person</Text>
</View>
))
}
</View>
)
}
}
function mapStateToProps(state) {
return{
people: state.people.people
}
}
function mapDispatchToProps (dispatch) {
return {
dispatchAddPerson: (person,age) => dispatch(addPerson(person,age)),
dispatchdeletePerson: (person) => dispatch(delPerson(person))
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App)
This line is incorrect in your reducer
case ADD_PERSON
return {
people: [
...state.people,
action.person,
action.age, <---this is wrong
],
};
shouldhave been
case ADD_PERSON
return {
people: [
...state.people,
action.person
],
};
The action could be like:
export const addPerson = (person, age) => {
return {
type: ADD_PERSON,
Object.assign({}, person , {age}),
}
}
Related
I want to create a Text element inside a View element, how do I link that? I've tried the following. (After typing inside the input, a search is made in the database and the result is translated into a text element).
class SearchScreen extends React.Component {
state = {
inputValue: "",
};
search() {
//Here I do the search in Firebase Realtime Database (it works)
var textElement = React.createElement(
Text,
{ style: { fontSize: 20 } },
[...] //Here inside is the retrieved data from the database
);
var resultView = useRef(resultView); //This doesn't work
ReactDOM.render(textElement, resultView);
}
setSearch = (inputValue) => {
this.setState({ inputValue }, () => this.search());
};
render() {
return (
<View>
<TextInput
onChangeText={(inputValue) => this.setSearch(inputValue)}
value={this.state.inputValue}
/>
<View ref="resultView">
</View>
</View>
)
}
}
export default SearchScreen;
ReactDom doesn't work with React Native.
Try something like this:
class SearchScreen extends React.Component {
state = {
inputValue: '',
resultText: '',
resultList: [],
};
search() {
//Here I do the search in Firebase Realtime Database (it works)
//[...] //Here inside is the retrieved data from the database
// Simulate request to the database
setTimeout(() => {
const databaseResultText = 'Hello World';
this.setState({
resultText: databaseResultText,
});
const databaseResultList = [
{
name: 'Bob',
},
{
name: 'Steve',
},
];
this.setState({
resultList: databaseResultList,
});
}, 1000);
}
setSearch = (inputValue) => {
this.setState({inputValue}, () => this.search());
};
render() {
return (
<View>
<TextInput
onChangeText={(inputValue) => this.setSearch(inputValue)}
value={this.state.inputValue}
/>
<View>
<Text>{this.state.resultText}</Text>
</View>
<View>
{this.state.resultList.map((item) => {
return <Text>{item.name}</Text>;
})}
</View>
</View>
);
}
}
export default SearchScreen;
Trying to learn Redux. I am building a list app. From the home screen you can see all your lists and click on one to update. You can also create a new list.
So I've made a check to see if you navigate to the list component with data, the action upon 'save' will be UPDATE_LIST. If you navigate to the list component with no data, the action upon 'save' will be NEW_LIST. The new list works but the update does not. If you need to see more files, let me know. Thank you.
This is the list component:
import React from 'react';
import { StyleSheet, Text, View, Button, TextInput } from 'react-native';
import { connect } from 'react-redux';
import { newList, updateList } from '../store/tagActions';
class List extends React.Component {
constructor(props){
super(props);
this.state = {
title: '',
tags: [],
mentions: [],
tagValue: '',
mentionValue: '',
id: null
}
}
submitTag = (text) => {
this.setState({
tags: [
...this.state.tags,
text
],
tagValue: ''
})
}
submitMention = (text) => {
this.setState({
mentions: [
...this.state.mentions,
text
],
mentionValue: ''
})
}
componentDidMount() {
if (this.props.route.params.data !== null) {
const { title, tags, mentions, id } = this.props.route.params
this.setState({
id: id,
title: title,
tags: tags,
mentions: mentions
})
} else return
}
save = () => {
if (this.props.route.params.data !== null) {
this.props.updateList(
id = this.state.id,
title = this.state.title,
tags = this.state.tags,
mentions = this.state.mentions
)
} else {
this.props.newList(
title = this.state.title,
tags = this.state.tags,
mentions = this.state.mentions
)
}
this.props.navigation.navigate('Home');
}
render() {
return (
<View style={styles.container}>
<TextInput //==================================== TITLE
value={this.state.title}
style={styles.title}
placeholder='add Title..'
onChangeText={text => this.setState( {title: text} ) }
/>
<View style={styles.allTags}>
<Text>{this.state.id}</Text>
<View style={styles.tagsList}>
{
this.state.tags.map((tag => (
<Text key={tag} style={styles.tags}>#{tag}</Text>
)))
}
</View>
<View style={styles.mentionsList}>
{
this.state.mentions.map((mention => (
<Text key={mention} style={styles.mentions}>#{mention}</Text>
)))
}
</View>
</View>
<TextInput // =================================== TAGS
value={ this.state.tagValue }
style={styles.tagsInput}
placeholder='add #Tags..'
placeholderTextColor = "#efefef"
autoCorrect = { false }
autoCapitalize = 'none'
onChangeText={text => this.setState( {tagValue: text}) }
onSubmitEditing={() => this.submitTag(this.state.tagValue)}
/>
<TextInput //===================================== MENTIONS
value={ this.state.mentionValue }
style={styles.mentionsInput}
placeholder='add #Mentions..'
placeholderTextColor = "#efefef"
autoCorrect = { false }
autoCapitalize = 'none'
onChangeText={text => this.setState( {mentionValue: text})}
onSubmitEditing= {() => this.submitMention(this.state.mentionValue)}
/>
<Button
title='save'
onPress={() => {
this.save();
}
}
/>
</View>
)
}
}
const mapStateToProps = (state) => {
return { state }
};
export default connect(mapStateToProps, { newList, updateList }) (List);
tagActions.js
let nextId = 0;
export const newList = (title, tags, mentions) => (
{
type: 'NEW_LIST',
payload: {
id: ++nextId,
title: title,
tags: tags,
mentions: mentions
}
}
);
export const updateList = (title, tags, mentions, id) => (
{
type: 'UPDATE_LIST',
payload: {
id: id,
title: title,
tags: tags,
mentions: mentions
}
}
);
tagReducer.js:
const tagReducer = (state = [], action) => {
switch (action.type) {
case 'NEW_LIST':
//add tags and mentions later
const { id, title, tags, mentions } = action.payload;
return [
...state,
{
id: id,
title: title,
tags: tags,
mentions: mentions
}
]
case 'UPDATE_LIST':
return state.map((item, index) => {
if (item.id === action.payload.id) {
return {
...item,
title: action.payload.title,
tags: action.payload.tags,
mentions: action.payload.mentions
}
} else { return item }
})
default:
return state;
}
};
export default tagReducer;
By sending args like so
export const updateList = (title, tags, mentions, id) => (
In the scope of the function, the first arg that the function will be called with gonna be title, and even by doing something like this
this.props.updateList(
id = this.state.id,
title = this.state.title,
tags = this.state.tags,
mentions = this.state.mentions
)
what you sent as this.state.id, gonna be evaluate as title. (not python alert)
so you have two options, either organize args as in function, or send object with keys
this.props.updateList({
id: this.state.id,
title: this.state.title,
tags: this.state.tags,
mentions: this.state.mentions
})
export const updateList = ({title, tags, mentions, id}) => (
Anyhow, of course you can use array as data structure for state, sorry I mislead you
const tagReducer = (state = [], action) => {
switch (action.type) {
const { id, title, tags, mentions } = action.payload || {};
case 'NEW_LIST':
//add tags and mentions later
return [ ...state, { id, title, tags, mentions } ]
case 'UPDATE_LIST':
return state.map(item =>
item.id === id ? { ...item, title, tags, mentions} : item
)
default: return state;
}
};
export default tagReducer;
I cannot get my reducer to update. I can step into the action when I fire this.completeQuiz(id) in my debugger but my state doesn't get updated. Any ideas?
import {
submitAnswer,
resetQuiz,
nextQuestion,
completeQuiz
} from "../actions/videos";
class TestYourselfScreen extends React.Component {
constructor(props) {
super(props);
this.onCapture = this.onCapture.bind(this);
}
completeQuiz = id => {
let video = this.props.videos.find(obj => obj.id == id);
let correctAnswers = video.results.correctAnswers;
const questionsFiltered = video.questions.filter(obj => obj.question != "");
completeQuiz({
id,
totalScore: correctAnswers.length / questionsFiltered.length
});
};
render() {
.....
return (
{questionsFiltered.length > 0 && !completed && (
<View
style={{
flex: 1
}}
>
....
<Button
title={lastQuestion ? "Finish" : "Next"}
buttonStyle={[styles.button]}
disabled={
!results.correctAnswers.includes(current) &&
!results.incorrectAnswers.includes(current)
? true
: false
}
onPress={() =>
lastQuestion ? this.completeQuiz(id) : this.next(id, current)
}
/>
</View>
)}
{completed === true && (
<View
style={{
flex: 1
}}
>
<ViewShot ref="viewShot" options={{ format: "jpg", quality: 0.9 }}>
...
</View>
)}
</ScrollView>
);
}
}
const mapStateToProps = state => {
return {
videos: state.tcApp.videos
};
};
const mapDispatchToProps = dispatch => ({
submitAnswer: data => dispatch(submitAnswer(data)),
resetQuiz: id => dispatch(resetQuiz(id)),
nextQuestion: data => dispatch(nextQuestion(data)),
completeQuiz: data => dispatch(completeQuiz(data))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(TestYourselfScreen);
Action:
export const completeQuiz = data => ({
type: "COMPLETE",
data
});
Reducer:
import { trimText } from "../helpers";
export function tcApp(
state = { videos: [], search: { videos: [], term: "" } },
action
) {
switch (action.type) {
....
case "COMPLETE": {
const { completed, totalScore, id } = action.data;
return {
videos: state.videos.map(video =>
video.id === id
? {
...video,
results: {
totalScore
},
completed: true
}
: video
),
search: { term: "", videos: [] }
};
}
default:
return state;
}
}
I think your action is available through props do it as this
completeQuiz = id => {
let video = this.props.videos.find(obj => obj.id == id);
let correctAnswers = video.results.correctAnswers;
const questionsFiltered = video.questions.filter(obj => obj.question != "");
this.props.completeQuiz({
id,
totalScore: correctAnswers.length / questionsFiltered.length
});
};
because we mapDispatchToProps
Hope it helps
Using
react-redux
redux-persist
redux-actions
react-native
I'm learning react-redux and react-native by myself.
I'm trying to update a single item from list in react-redux now.
There's many categories and stores in my project.
An user would select one of categories, and then click 'like button' of one item in store list.
It's like instagram or facebook.
When I change one item's state, the state of every item in the store list change at the same time.
I have no idea why it happens.
I set the structure to ducks pattern to avoid change too much files when to change state.
If anyone give some advice, I would appreciate and it could be helpful for me. Thank you.
I saw some article to resolve this issue, have to give id to distinguish items and make the payload as object. I didn't understand well, so my code is messy now. But I'd like to know what happen to my code, so I share my code.
restaurantContainer.js
class RestaurantListContainer extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.imgUrls !== this.props.imgUrls;
}
componentDidMount() {
const {category, StoreActions} = this.props;
try {
StoreActions.fetchStoreInfo(category);
StoreActions.fetchImgUrl(category);
this.getUid();
} catch (e) {
console.log(e);
}
}
async getUid() {
const {StoreActions} = this.props;
const uid = await storage.getItem('uid');
StoreActions.fetchLikeList(uid);
}
render() {
const {fetching, tabColor, tabName, category, categoryId, navigation, stores, imgUrls, like} = this.props;
const {onClick} = this;
return (
<View>
...
<ScrollView>
{
fetching ?
<View>
<Bars size={30} color="#40D59B"/>
</View>
:
stores.map((store, i) =>
<View key={`item-${i}`}>
<RestaurantItem
key={`restaurantItem-${i}`}
i={i}
category={category}
navigation={navigation}
storeInfo={store}
imgUrls={imgUrls[i]}
categoryId={categoryId}
like={like}
/>
</View>
)
}
</ScrollView>
</View>
);
}
}
export default connect(
({styleMod, storeMod}) => ({
stores: storeMod.stores,
imgUrls: storeMod.imgUrls,
fetching: storeMod.fetching,
likeList: storeMod.likeList
}),
(dispatch) => ({
StoreActions: bindActionCreators(storeActions, dispatch),
})
)(RestaurantListContainer);
restaurantItem.js
class RestaurantItem extends Component {
pressFunc = (item) => {
const {navigation} = this.props;
const {push} = navigation;
console.log(item.name);
push('RestaurantDetail', {info: item});
}
voteAdder = async (storeName) => {
const uid = await storage.getItem('uid');
const {i, categoryId} = this.props;
if (uid) {
const {VoteActions, LikeActions, category, favoriteStores} = this.props;
try {
VoteActions.voteAdd(favoriteStores, category, storeName, uid);
LikeActions.likeClicked(storeName, category, categoryId, i);
} catch (e) {
console.log(e);
}
} else {
alert('You are not authorized!');
}
}
render() {
const {i, storeInfo, category, categoryId, imgUrls, favoriteStores, like} = this.props;
return (
<View style={restaurantCard}>
<StoreImg
img={imgUrls}
name={storeInfo.name}
/>
<StoreInfoBlock
i={i}
storeInfo={storeInfo}
pressFunc={this.pressFunc}
/>
<View style={{flexDirection: 'column'}} >
{
<ThumbImg
voteAdder={() => this.voteAdder(storeInfo.name)}
name={storeInfo.name}
favoriteStore={favoriteStores[category]}
category={category}
like={like}
categoryId={categoryId}
/>
}
<Score count={storeInfo.count}/>
</View>
</View>
);
}
}
export default connect(
({voterMod, likeMod}) => ({
favoriteStores: voterMod.favoriteStores,
like: likeMod.like,
}),
(dispatch) => ({
VoteActions: bindActionCreators(voteActions, dispatch),
LikeActions: bindActionCreators(likeActions, dispatch),
})
)(RestaurantItem);
thumbImg.js
export default class ThumbImg extends Component {
onClick = () => {
this.props.voteAdder();
}
onFlag = () => {
const {like, categoryId, i} = this.props;
if(like.categoryById[categoryId]) {
if(like.storeById[i]) {
console.log(2);
return (
<FastImage
resizeMode={FastImage.resizeMode.cover}
style={{width: 50, height: 50}}
source={require('...')}
/>
);
} else {
return (
<FastImage
resizeMode={FastImage.resizeMode.cover}
style={{width: 50, height: 50}}
source={require('...')}
/>
);
}
} else {
return (
<FastImage
resizeMode={FastImage.resizeMode.cover}
style={{width: 50, height: 50}}
source={require('...')}
/>
);
}
}
render() {
return (
<TouchableOpacity onPress={this.onClick}>
<View style={{paddingTop: 15, paddingRight: 15}}>
{
this.onFlag()
}
</View>
</TouchableOpacity>
);
}
}
likeMod.js
// Action types
const ON_LIKE = 'like/ON_LIKE';
const OFF_LIKE = 'like/OFF_LIKE';
// action creator
export const likeClicked = (store, category, categoryId, i) => (dispatch) => {
const selectedCategory = {categoryById: {}};
const categoryInfo = {
id: categoryId,
name: category,
};
selectedCategory.categoryById[categoryId] = categoryInfo;
const selectedStore = {storeById: {}};
const storeInfo = {
id: i,
name: store
}
selectedStore.storeById[i] = storeInfo;
const favorites = {
...selectedCategory,
...selectedStore
}
dispatch({type: ON_LIKE, payload: favorites});
}
const initialState = {
like: {
categoryById: {},
storeById: {}
}
};
// Reducer
export default handleActions({
[ON_LIKE]: (state, action) => ({...state, like: action.payload}),
[OFF_LIKE]: (state, action) => ({...state, like: action.payload}),
}, initialState);
How do I target which element/object in array in a reducer I have to pass the action to? I am trying to figure out what action I take on the reducer. Or how do I change affect the number value in the object. So I am rendering the array in a View with TouchableOpacity which onPress Im calling the action dispatch as following:
import React, { Component } from 'react';
import { Text, View, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
class NewData extends Component {
render(){
const renData = this.props.newData.map((data, idx) => {
return (
<View key={idx}>
<TouchableOpacity
onPress={() => this.props.incNum(data.id)}
//not sure how to toggel dispatch call above, for onPress
>
<Text style={styles.welcome}>{data.id} - {data.name} - {data.number}</Text>
</TouchableOpacity>
</View>
)
});
return(
<View>
{renData}
</View>
);
}
}
function mapStateToProps (state) {
return {
newData: state.newData
};
};
//I think I need to create a toggle here:
//Something like: this.props.newData.id ? incNum : decNum
function mapDispatchToProps (dispatch) {
return {
incNum: (id) => {
dispatch({
type: "INC_NUM",
payload: id
})
},
decNum: (id) => {
dispatch({
type: "DEC_NUM",
payload: id
})
}
};
};
export default connect( mapStateToProps, mapDispatchToProps )( NewData )
My Reducer:
const initialState = [
{
id: '01',
name: 'Name One',
number: 11
},
{
id: '02',
name: 'Name Two',
number: 22
},
{
id: '03',
name: 'Name Three',
number: 33
}
]
export default function newData (state = initialState, action) {
switch (action.type) {
case "INC_NUM":
return {
...state,
number: this.state.number ++ // <== need help here.
}
case "DEC_NUM":
return {
...state,
number: this.state.number --
}
default:
return state
}
}
I think <TouchableOpacity onPress={() => this.props.incNum(data.id)} will bind the id to be changed. And passing the payload: id will pass the id to the reducer. But how do I update the value in the reducer? I can do without the toggle for now. I want to learn about passing the value and updating accordingly in the reduder.
Thank you.
EDIT1 :
So my reducer now is:
const initialState = {
1: {
id: '01',
name: 'Name One',
number: 11
},
2: {
id: '02',
name: 'Name Two',
number: 22
}
}
export default function newData (state = initialState, action) {
switch (action.type) {
case "INC_NUM":
return {
...state,
[action.payload]: {
...state[action.payload],
number: state[action.payload].number++ <== Line 58 in ERROR screenshot
}
case "DEC_NUM":
return {
...state,
[action.payload]: {
...state[action.payload],
number: state[action.payload].number--
}
default:
return state
}
}
And the way I'm rendering it is:
class NewData extends Component {
render(){
const renData = Object.keys(this.props.newData).map((key, idx) => {
let data = this.props.newData[key]
return (
<View key={idx}>
<TouchableOpacity
onPress={() => this.props.updateNum(data.id)}
>
<Text style={styles.welcome}>{data.id} - {data.name} - {data.number}</Text>
</TouchableOpacity>
</View>
)
});
return(
<View>
{renData}
</View>
)
}
}
function mapDispatchToProps (dispatch) {
return {
updateNum: (id) => {
INC_NUM({
type: "INC_NUM",
payload: id
})
}
};
};
Everything in the reducer is appearing as expected. But when I click and the action is called, I get an error:
your reducer seems to be lot more complex that it needs to be, below is the simpler approach, hope this helps.
const initialState = {
1: {
id: '01',
name: 'Name One',
number: 11
},
2: {
id: '02',
name: 'Name Two',
number: 22
}
}
export default function newData (state = initialState, action) {
switch (action.type) {
case "INC_NUM":
const newState = {...state};
newState[action.payload].nubmer++;
return newState;
case "DEC_NUM":
const newState = {...state};
newState[action.payload].nubmer--;
return newState;
default:
return state
}
}
Your problem appears to be in your reducer and the structure of your state.. you're not using the ID to identify which keyval pair to update.
First I'd modify your initialState to be an object and use the id as your keys:
const initialState = {
1: {name: 'Name One', number: 11},
2: {name: 'Name Two', number: 22},
3: {name: 'Name Three', number: 33}
}
Then in your reducer:
case "INC_NUM":
return {
...state,
[action.id]: {
...state[action.id],
number: state[action.id].number++
}
}
I know the syntax is convoluted, but the spread operator is nice shorthand for what otherwise would be even harder to read.