I want to increase and decrease the counter.counter1 and counter.counter2.innerCount by input value.
Here is the error I found now
I am weak at destructing object etc. and now learning for it.
Could provide me any advice or code? Especially for increment and decrement for innerCount. Much appreciate.
actionCreators.js
import * as actionTypes from "../actionTypes";
export const incrementCounter1 = () => {
return {
type: ActionTypes.INCREMENT_COUNTER_1,
};
};
export const decrementCounter1 = () => {
return {
type: ActionTypes.DECREMENT_COUNTER_1,
};
};
export const incrementByAmount = (amount) => {
return {
type: ActionTypes.INCREMENT_BY_AMOUNT,
amount:amount,
};
};
reducer.js
import * as actionTypes from "../actionTypes";
const INITIAL_STATE = {
counter: {
counter1: 0,
counter2: {
innerCount: 0,
},
},
};
export const Auth = (state = INITIAL_STATE, action) => {
const { type, payload } = action;
let a;
switch (type) {
case ActionTypes.INCREMENT_COUNTER_1:
a = {
...state,
counter: {
...state.counter,
counter1: state.counter.counter1 +=1,
},
};
return a;
case ActionTypes.DECREMENT_COUNTER_1:
a = {
...state,
counter: {
...state.counter,
counter1: state.counter.counter1 -=1,
},
};
return a;
case ActionTypes.INCREMENT_BY_AMOUNT:
a = {
...state,
counter: {
...state.counter,
counter1: state.counter.counter1 +=payload,
},
};
return a;
default:
return state;
}
};
export default Auth;
mainPage.js
import React, { useState } from "react";
import { View, Text, StyleSheet, Button, TextInput } from "react-native";
import {
incrementCounter1,
decrementCounter1,
incrementByAmount,
} from "./states/redux/ActionCreators/auth";
import { connect } from "react-redux";
const Counter = ({
counterRedux,
incrementCounter1,
decrementCounter1,
incrementByAmount,
}) => {
const [amount, setAmount] = useState('');
return (
<View>
<Text style={styles.text}>Input text for changing</Text>
<Button title="INCREMENT" onPress={() => incrementCounter1()} />
<Button title="DECREMENT" onPress={() => decrementCounter1()} />
<View>
<Text style={styles.Atext}>Enter amount to increase:</Text>
<TextInput style={styles.input} value={amount} onChangeText={(a) => setAmount(a)} />
<Text style={styles.Atext}>Amount: {amount}</Text>
<Button title='Add Amount' onPress={(amount)=>incrementByAmount(amount)}></Button>
</View>
<View>
<Text style={styles.Atext}>First Counter: {counterRedux.counter1}</Text>
</View>
</View>
);
};
const mapStateToProps = (state) => {
return {
counterRedux: state.counter,
};
};
const mapDispatchToProps = (dispatch) => {
return {
incrementCounter1: () => dispatch(incrementCounter1()),
decrementCounter1: () => dispatch(decrementCounter1()),
incrementByAmount: (amount) => dispatch(incrementByAmount(amount)),
};
};
const styles = StyleSheet.create({
text: {
fontSize: 25,
},
Atext: {
fontSize: 20,
},
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
input: {
borderWidth: 1,
borderColor: "#777",
padding: 8,
margin: 10,
width: 200,
},
button: {
backgroundColor: "#fff",
fontSize: 15,
},
});
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
actionTypes.js
export const INCREMENT_BY_AMOUNT = 'INCREMENT_BY_AMOUNT';
export const INCREMENT_COUNTER_1 = 'INCREMENT_COUNTER_1';
export const DECREMENT_COUNTER_1 = 'DECREMENT_COUNTER_1';
Your first issue is here: <Button title='Add Amount' onPress={(amount)=>incrementByAmount(amount)}></Button>.
What you are doing is passing to incrementByAmount the argument passed by onPress which is not at all the amount you expect, but a PressEvent object.
In order to receive the amount you expect, you need to do this: <Button title='Add Amount' onPress={() => incrementByAmount(amount)}></Button> so you get the amount from useState.
Also you definitely don't need to have three actions for your counter, a simpler way to do it would be to have an updateAmount function to which you pass as payload a type that would be "increment" or "decrement", and an amount.
An even simpler way would be to only have an amount and pass either a negative or a positive value to it.
For your increment and decrement buttons, you would simply need to pass 1 or -1 as amount.
Your second issue is that you are mutating your state in your reducer with the += and -= operators.
Here is your fixed reducer (I will let you implement the changes I suggested earlier though):
import * as actionTypes from "../actionTypes";
const INITIAL_STATE = {
counter: {
counter1: 0,
counter2: {
innerCount: 0,
},
},
};
export const Auth = (state = INITIAL_STATE, action) => {
const { type, payload } = action;
switch (type) {
case ActionTypes.INCREMENT_COUNTER_1:
return {
...state,
counter: {
...state.counter,
counter1: state.counter.counter1 + 1,
},
};
case ActionTypes.DECREMENT_COUNTER_1:
return {
...state,
counter: {
...state.counter,
counter1: state.counter.counter1 - 1,
},
};
case ActionTypes.INCREMENT_BY_AMOUNT:
return {
...state,
counter: {
...state.counter,
counter1: state.counter.counter1 + payload,
},
};
default:
return state;
}
};
export default Auth;
I removed the a variable that wasn't needed and changed the += operator to + and the -= operator to - so your state isn't mutated.
Your third issue is that you are destructuring a variable payload while it is called amount in your action creator.
Also don't forget that you are getting a string from <TextInput> and not a number.
Finally you import your action types as actionTypes but use them as ActionTypes.
Related
I have a react native prodject.
I have an input that I use in several places in the app. I can type in it no problem, but I use siri dictation fromt the ios keyboard, the words are cut short by a rerender.
A similar questions has been asked before,
React native dictation cuts words abruptly on iOS
but the only answer was for class components. Is there a way to fix this with functional components?
I tried throwing a useMemo() around the textChangeHandler, and that does allow siri to work, by blocking all state updates. But this is no good because then I have no data.
Here is my component:
import React, { useReducer, useEffect, useRef } from 'react';
import {
StyleSheet,
View,
Text,
TextInput,
TouchableOpacity,
} from 'react-native';
import mergeRefs from 'react-merge-refs';
import PropTypes from 'prop-types';
const INPUT_CHANGE = 'INPUT_CHANGE';
const INPUT_BLUR = 'INPUT_BLUR';
const formatDate = date => {
const options = {
month: 'numeric',
day: 'numeric',
year: '2-digit',
};
const formattedDate = new Date(date);
const _formattedDate = formattedDate.toLocaleDateString('en-US', options);
return _formattedDate;
};
const inputReducer = (state, action) => {
switch (action.type) {
case INPUT_CHANGE:
return {
...state,
value: action.value,
isValid: action.isValid,
};
case INPUT_BLUR:
return {
...state,
touched: true,
};
default:
return state;
}
};
const Input = React.forwardRef((props, ref) => {
const [inputState, dispatch] = useReducer(inputReducer, {
value: props.initialValue ? props.initialValue : '',
isValid: props.initiallyValid ? props.initiallyValid : true,
touched: props.initialValue ? true : false,
});
const { onInputChange, id } = props;
useEffect(() => {
onInputChange(id, inputState.value, inputState.isValid);
}, [inputState, onInputChange, id]);
const textChangeHandler = text => {
const emailRegex =
/^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
let isValid = true;
if (props.required && text.trim().length === 0) {
isValid = false;
}
if (props.email && !emailRegex.test(text.toLowerCase())) {
isValid = false;
}
if (props.min != null && +text < props.min) {
isValid = false;
}
if (props.max != null && +text > props.max) {
isValid = false;
}
if (props.minLength != null && text.length < props.minLength) {
isValid = false;
}
dispatch({ type: INPUT_CHANGE, value: text, isValid: isValid });
};
const lostFocusHandler = () => {
dispatch({ type: INPUT_BLUR });
};
const inputRef = useRef();
const getFormValue = () => {
const inputValue = props.initialValue
? props.initialValue
: inputState.value;
if (props.date) {
return formatDate(inputValue).toString();
}
return inputValue;
};
return (
<View style={{ ...props.style, ...styles.container }}>
<TextInput
ref={mergeRefs([inputRef, ref])}
{...props}
value={getFormValue()}
onChangeText={textChangeHandler}
onBlur={lostFocusHandler}
/>
{!inputState.isValid && inputState.touched && (
<TouchableOpacity onPress={() => inputRef.current.focus()}>
<View style={{ ...props.style, ...styles.errorContainer }}>
<Text
testID="Auth.errorMessage"
style={{ color: props.errorTextColor, ...styles.errorText }}
>
{props.errorText}
</Text>
</View>
</TouchableOpacity>
)}
</View>
);
});
const styles = StyleSheet.create({
container: { flex: 1 },
errorContainer: {
marginVertical: 5,
},
errorText: {
fontSize: 13,
},
});
Input.displayName = 'Input'; //This is here only to make esLint happy
Input.propTypes = {
date: PropTypes.bool,
onInputChange: PropTypes.func,
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
initialValue: PropTypes.any,
initiallyValid: PropTypes.bool,
required: PropTypes.bool,
email: PropTypes.bool,
min: PropTypes.number,
max: PropTypes.number,
minLength: PropTypes.number,
style: PropTypes.object,
errorText: PropTypes.string,
errorTextColor: PropTypes.string,
};
export default Input;
Well, I guess I forgot to answer this before I figured out a solution. It's a bit hacky, but I just wrapped the contents of getFormValue in a 5ms timeout, and that was enough to keep it from bugging out.
const getFormValue = () => {
setTimeout(() => {
const inputValue = props.initialValue
? props.initialValue
: inputState.value;
if (props.date) {
return formatDate(inputValue).toString();
}
return inputValue;
}, 5);
};
I have a big problem with React TimeLines Package(https://openbase.com/js/react-timelines)
I want something like this photo:
( having 3 P tags with different ClassNames)
but in default case of this package I cant do it!
I think I should use something like createElement and textContent in JS. but I dont know how!
My Codes:
import React, { Component } from "react";
import Timeline from "react-timelines";
import "react-timelines/lib/css/style.css";
import { START_YEAR, NUM_OF_YEARS, NUM_OF_TRACKS } from "./constant";
import { buildTimebar, buildTrack } from "./builder";
import { fill } from "./utils";
const now = new Date("2021-01-01");
const timebar = buildTimebar();
// eslint-disable-next-line no-alert
const clickElement = (element) =>
alert(`Clicked element\n${JSON.stringify(element, null, 2)}`);
class App extends Component {
constructor(props) {
super(props);
const tracksById = fill(NUM_OF_TRACKS).reduce((acc, i) => {
const track = buildTrack(i + 1);
acc[track.id] = track;
return acc;
}, {});
this.state = {
open: false,
zoom: 2,
// eslint-disable-next-line react/no-unused-state
tracksById,
tracks: Object.values(tracksById),
};
}
handleToggleOpen = () => {
this.setState(({ open }) => ({ open: !open }));
};
handleToggleTrackOpen = (track) => {
this.setState((state) => {
const tracksById = {
...state.tracksById,
[track.id]: {
...track,
isOpen: !track.isOpen,
},
};
return {
tracksById,
tracks: Object.values(tracksById),
};
});
};
render() {
const { open, zoom, tracks } = this.state;
const start = new Date(`${START_YEAR}`);
const end = new Date(`${START_YEAR + NUM_OF_YEARS}`);
return (
<div className="app">
<Timeline
scale={{
start,
end,
zoom,
}}
isOpen={open}
toggleOpen={this.handleToggleOpen}
clickElement={clickElement}
timebar={timebar}
tracks={tracks}
now={now}
enableSticky
scrollToNow
/>
</div>
);
}
}
export default App;
builder.js:
export const buildElement = ({ trackId, start, end, i }) => {
const bgColor = nextColor();
const color = colourIsLight(...hexToRgb(bgColor)) ? "#000000" : "#ffffff";
return {
id: `t-${trackId}-el-${i}`,
title: "Bye Title: Hello Type: String",
start,
end,
style: {
backgroundColor: `#${bgColor}`,
color,
borderRadius: "12px",
width: "auto",
height: "120px",
textTransform: "capitalize",
},
};
};
Every element of the array should be displayed for some time and the time for which each element is displayed should be determined by a value in each element.
let array=[{display:"a",time:10},{display:"b",time:15},{display:"c",time:22}]
class App extends React.Component{
state={stateDisplay:"",
stateTime:""
}
componentWillMount(){
var i=0;
let handle=setInterval(()=>{
var element= array[i]
this.setState({
stateDisplay:element.display,
stateTime:element.time,
})
i=i+1;
if(i===array.length){
clearInterval(handle)
}
},10000)
}
render(){
return(
<div> {this.state.stateDisplay} </div>
)}}
i have done something like this but using setinterval the delay can only be set for a constant time,here 10s.
I want the first element to display for 10s and then the next element for 15s, third for 22s which is the time value for each element of the array.
I know i cant do that using setinterval is there a way to do this using Settimeout?
This was almost like a little challenge, heres what i managed to come up with, its in typescript, if you need js, just remove interfaces and type annotations
/* eslint-disable #typescript-eslint/no-explicit-any */
/* eslint-disable prettier/prettier */
/* eslint-disable no-shadow */
/* eslint-disable no-console */
import React, { FC, useState, useEffect, useCallback } from 'react';
import { View, Button, Text } from 'react-native';
interface Data {
duration: number;
bgColor: string;
}
const dataArr: Data[] = [
{ duration: 3, bgColor: 'tomato' },
{ duration: 6, bgColor: 'skyblue' },
{ duration: 9, bgColor: 'gray' },
];
const Parent = () => {
const [currentIdx, setCurrentIdx] = useState<number>(0);
const [elementData, setElementData] = useState<Data>(dataArr[currentIdx]);
useEffect(() => {
console.log('idx', currentIdx);
if (currentIdx > dataArr.length) return;
setElementData({ ...dataArr[currentIdx] });
}, [currentIdx]);
const pushNext = () => {
setCurrentIdx(currentIdx + 1);
};
const handleRestart = () => {
setCurrentIdx(0);
setElementData({ ...dataArr[0] });
};
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Timer
data={elementData}
onCountDownComplete={pushNext}
restart={handleRestart}
/>
</View>
);
};
interface Props {
data: Data;
onCountDownComplete: () => void;
restart: () => void;
}
const Timer: FC<Props> = ({ data, onCountDownComplete, restart }) => {
const [seconds, setSeconds] = useState<number>(data.duration);
// update on data change
useEffect(() => {
setSeconds(data.duration);
}, [data]);
const callback = useCallback(() => {
onCountDownComplete();
}, [onCountDownComplete]);
useEffect(() => {
let interval: any = null;
if (seconds > -1) {
interval = setInterval(() => {
if (seconds - 1 === -1) {
callback();
} else {
setSeconds(seconds - 1);
}
}, 1000);
} else {
return;
}
return () => {
clearInterval(interval);
};
}, [seconds, callback]);
return (
<View
style={{ backgroundColor: data.bgColor, padding: 16, borderRadius: 10 }}
>
<Text style={{ marginBottom: 24 }}>{seconds}</Text>
<Button title="restart" onPress={restart} />
</View>
);
};
I have built this app using create-react-native-app, the action is dispatched but the state isn't being updated and I'm not sure why.
I see the action being logged (using middleware logger) but the store isn't getting updated, I am working on Add_Deck only for now
Here is my reducer:
// import
import { ADD_CARD, ADD_DECK } from './actions'
// reducer
export default function decks(state ={}, action){
switch(action.type){
case ADD_DECK:
return {
...state,
[action.newDeck.id]: action.newDeck
}
case ADD_CARD:
return {
...state,
[action.deckID]: {
...state[action.deckID],
cards: state[action.deckID].cards.concat([action.newCard])
}
}
default: return state
}
}
Actions file:
// action types
const ADD_DECK = "ADD_DECK";
const ADD_CARD = "ADD_CARD";
// generate ID function
function generateID() {
return (
"_" +
Math.random()
.toString(36)
.substr(2, 9)
);
}
// action creators
function addDeck(newDeck) {
return {
type: ADD_DECK,
newDeck
};
}
// export
export function handleAddDeck(title) {
return dispatch => {
const deckID = generateID();
// const newDeck = { id: deckID, title, cards: [] };
dispatch(addDeck({ id: deckID, title, cards: [] }));
};
}
function addCard(deckID, newCard) {
// { question, answer }, deckID
return {
type: ADD_CARD,
deckID,
newCard
};
}
// export
export function handleAddCard(deckID, content) {
// { question, answer }, deckID
return dispatch => {
const newCard = { [generateID()]: content };
dispatch(addCard(deckID, newCard));
};
}
And react-native component:
import React, { Component } from 'react';
import { View, Text, StyleSheet, TextInput, TouchableOpacity } from "react-native";
import {red, white} from '../utils/colors'
import { connect } from 'react-redux'
import { handleAddDeck } from '../redux/actions'
class AddDeck extends Component {
state = {
text:""
}
handleSubmit = () => {
this.props.dispatch(handleAddDeck(this.state.text))
this.setState(()=>{
return { text: ""}
})
}
render() {
return (
<View style={styles.adddeck}>
<Text> This is add deck</Text>
<TextInput
label="Title"
style={{ height: 40, borderColor: "gray", borderWidth: 1 }}
onChangeText={text => this.setState({ text })}
placeholder="Deck Title"
value={this.state.text}
/>
<TouchableOpacity style={styles.submitButton} onPress={this.handleSubmit}>
<Text style={styles.submitButtonText}>Create Deck</Text>
</TouchableOpacity>
</View>
);
}
}
function mapStateToProps(decks){
console.log("state . decks", decks)
return {
decks
}
}
export default connect(mapStateToProps)(AddDeck);
const styles = StyleSheet.create({
adddeck: {
marginTop: 50,
flex: 1
},
submitButton: {
backgroundColor: red,
padding: 10,
margin: 15,
height: 40,
},
submitButtonText: {
color: white
}
});
I guess you forgot to export your types from the actions file thus the switch(action.type) does not trigger the needed case statement.
Maybe try to add as the following:
export const ADD_DECK = "ADD_DECK";
export const ADD_CARD = "ADD_CARD";
Or further debugging just to see if the values are the ones what you are looking for:
export default function decks(state = {}, action) {
console.log({type:action.type, ADD_DECK}); // see what values the app has
// further code ...
}
I hope that helps! If not, let me know so we can troubleshoot further.
I would like to create a infinite/loop react-native Picker like on the image below.
So, my question is:
When I'm scrolling, how can I make the Picker start again from the first item after reach out the last item?
Here's my code:
render() {
const hourItems = [];
for(var i = 0; i < 24; i++) {
hourItems.push(
<Picker.Item label={i.toString()} value={i} key={i} />
);
}
return(
<ScrollView style={styles.panel}>
<Picker
selectedValue={this.state.hour}
onValueChange={(itemValue, itemIndex) => this.setState({ hour: itemValue })}
>
{hourItems}
</Picker>
</ScrollView>
);
}
Create redux action which increment value of the minutes and hours. Inside that action you can check whether it should be reset, for example:
export const tick = () => (dispatch, getState) => {
const { minute } = getState().clock
if (minute === 59) {
dispatch({ type: RESET_MINUTE})
dispatch({ type: INCREMENT_HOUR})
} else {
dispatch({ type: INCREMENT_MINUTE})
}
}
and in the reducer:
const createReducer = (initialState, handlers) =>
(state = initialState, action) => {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action)
}
return state
}
export default createReducer(initialState, {
INCREMENT_HOUR(state, action) {
return {
...state,
hour: state.hour + 1,
}
},
INCREMENT_MINUTE(state, action) {
return {
...state,
minute: state.minute + 1,
}
},
RESET_MINUTE(state) {
return {
...state,
minute: 0,
}
},
})
then connect your component with the reducer and the update should be automatic :)