I am learning redux. I am testing it in todo app. Everything works fine but how can I display the saved todos in the beginning. I've used AsyncStorage to store the todos list in reducer and tried to read them in mapstatetoProps function but in vain. How can I do that. Thankyou
store
import { createStore } from "redux";
import rootReducer from "../reducers";
export default (store = createStore(rootReducer));
reducer
let nextId = 0;
let newState = [];
saveTodos = newToDos => {
const saveTodos = AsyncStorage.setItem(
"savedToDoList",
JSON.stringify(newToDos)
);
};
const todos = (state = [], action) => {
switch (action.type) {
case "ADD_TODO":
newState = [
...state,
{
id: nextId++,
text: action.todo,
completed: false
}
];
this.saveTodos(newState);
return newState;
case "TOGGLE_TODO":
return state.map(
todo =>
todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
);
default:
return state;
}
};
visibleTodo.js
const mapStateToProps = state => {
return {
todoss: state.todos
};
};
const mapDispatchToProps = dispatch => ({
toggleTodo: id => dispatch({ type: "TOGGLE_TODO", id })
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ToDoList);
ToDoList.js
const ToDoList = ({ todoss, toggleTodo }) => (
<View style={{ padding: 20 }}>
{todoss.map(todo => (
<TouchableOpacity key={todo.id} onPress={() => toggleTodo(todo.id)}>
<Text
style={{
fontSize: 16,
color: "gray",
textDecorationLine: todo.completed ? "line-through" : "none"
}}
>
{todo.id + 1 + ". " + todo.text}
</Text>
</TouchableOpacity>
))}
</View>
);
I think that what you are looking for is redux-persist, so every time you create your store it will try to persist with async storage getting the previous data and when you add something to your store, it'll be persisted to async storage.
Here is a very good tutorial for that https://blog.reactnativecoach.com/the-definitive-guide-to-redux-persist-84738167975
Note: if you don't have nothing in your async storage yet, you'll need to add a todo first
Related
need some serious help here. I am currently using a class component to add my items to cart but it looks like i keep getting undefined, can someone point me in the right direction please?
Homepage :
const mapStateToProps = state => ({
cartItems: state.cart.cartItems
});
const mapDispatchToProps = dispatch => {
return {
addToCartHandler: item => {
dispatch(addToCart(item))
}
}
}
renderRow =({item}) =>{
console.log(this.state.cartItems)
return(
<TouchableHighlight >
<View style ={styles.card}>
<Image source={{uri:item.image}} style={styles.image} />
<Icon name="add" size ={20} style= {styles.addToCartBtn} onPress ={() => this.props.addToCartHandler(item)} />
</TouchableHighlight>
)
}
reducers
import { ADD_TO_CART } from '../constants'
const initialState = {
cartItems: [],
totalPrice :0
}
//add item
export const cart = (state = initialState, action) => {
switch (action.type) {
case ADD_TO_CART:
console.log("reducer",action)
var {item} =action.payload;
var newState =Object.assign({},{...state});
for (var i = 0; i < state.cartItems.length; i++) {
newState.cartItems = newState.cartItems.concat(item);
console.log(newState)
return newState
}
default:
return state
}
}
action
import {ADD_TO_CART} from '../constants'
import {REMOVE_FROM_CART} from '../constants'
export const addToCart =(item) =>{
// console.log(item)
return{
type: ADD_TO_CART,
payload : item,
};
}
Dont think anything is wrong with my action or rootreducers. I am not sure why i keep getting undefined whenever i add to cart when i can console.log the item
You are not creating the new state correctly, I think the following will work:
case ADD_TO_CART:
console.log('reducer', action);
var item = action.payload;
return {
...state,
cartItems:state.cartItems.concat(item)
}
Here is some useful information on how to create a new state.
I have this page (screen) that receives via Params an ID number, in this Screen, I try to call an Action Function from my Action (reducer) file and gets an API call, I thought I didn't get any information in the Array from that call, I believe that the issue was in the Call, but I put a Console log after the declaration on the Action Function, but it didn't print so I think it didn't access to that function, so I believe the issue is in the Call of that function via Dispatch.
I even tried to put a Breakpoint inside the UseEfect where I call the Function that calls the Dispatch Function but it never breaks I'm not sure where is the error, this is the Code:
Screen (where I suspect the issue is):
```import React, {useState, useCallback, useEffect} from 'react';
import { ScrollView, Text, Image, StyleSheet, View } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
const ProductDetailScreen = props => {
const playerId = props.route.params.id;
const estadId = props.route.params.statId;
const selectedPlayer = useSelector(state => state.jugadores.availablePlayers.find(prod => prod.id === playerId));
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [error, setError] = useState();
const goles = useSelector(state => state.jugadores.playerGoals);
const dispatch = useDispatch();
const loadEstad = useCallback (async (param) => {
setError(null);
setIsRefreshing(true);
try {
await dispatch(userActions.fetchEstadistica(param));
} catch (err){
setError(err.message);
}
setIsRefreshing(false);
}, [dispatch, setIsLoading, setError]);
useEffect(() => {
setIsLoading(true);
loadEstad(estadId).then(() => {
setIsLoading(false);
});
}, [dispatch, loadEstad]);
console.log(estadId);
console.log(goles);
return (
<ScrollView>
<Image style={styles.image} source={{ uri: selectedPlayer.imagen }} />
<View style={styles.dataContainer}>
<Text style={styles.description}>Numero: <Text style={styles.subtitle}>{selectedPlayer.numero}</Text></Text>
<Text style={styles.description}>Nombre Completo: <Text style={styles.subtitle}>{selectedPlayer.nombre_completo}</Text></Text>
<Text style={styles.description}>Posicion: <Text style={styles.subtitle}>{selectedPlayer.posicion}</Text> </Text>
<Text style={styles.description}>Edad: <Text style={styles.subtitle}>{selectedPlayer.edad}</Text></Text>
<Text style={styles.description}>Nacionalidad: <Text style={styles.subtitle}>{selectedPlayer.nacionalidad}</Text></Text>
</View>
</ScrollView>
);
}
;
export const screenOptions = navData => {
return {
headerTitle: navData.route.params.nombre,
}
};
const
styles = StyleSheet.create({
image: {
width: '100%',
height: 300,
},
subtitle: {
fontSize: 16,
textAlign: 'justify',
marginVertical: 20,
fontWeight:'normal',
},
description: {
fontSize: 16,
textAlign: 'center',
marginVertical: 20,
fontWeight: 'bold',
},
dataContainer:{
width: '80%',
alignItems: 'center',
marginHorizontal: 40,
},
actions: {
marginVertical: 10,
alignItems: 'center',
},
});
export default ProductDetailScreen
;```
This is my Action File:
import ResultadoEstadistica from '../../models/estadistica/resultadoEstadistica';
import PlayerEstadistica from '../../models/estadistica/playerEstatisticData';
import Cards from '../../models/estadistica/cards';
import Games from '../../models/estadistica/games';
import Goals from '../../models/estadistica/goals';
export const SET_JUGADORES = 'SET_JUGADORES';
export const SET_ESTADISTICA = 'SET_ESTADISTICA';
export const fetchJugadores = () => {
return async (dispatch) => {
//any async code here!!!
try {
const response = await fetch(
'https://alianzafc2021-default-rtdb.firebaseio.com/jugadores.json'
);
if (!response.ok) {
throw new Error('Algo salio Mal!');
}
const resData = await response.json();
const loadedJugadores = [];
for (const key in resData) {
loadedJugadores.push(
new Jugador(
key,
resData[key].altura,
resData[key].apellido,
resData[key].edad,
resData[key].fecha_nacimiento,
resData[key].iso_code,
resData[key].imagen,
resData[key].lugar_nacimiento,
resData[key].nacionalidad,
resData[key].nombre_completo,
resData[key].nombre_corto,
resData[key].nombres,
resData[key].numero,
resData[key].pais,
resData[key].peso,
resData[key].player_id,
resData[key].posicion
)
);
}
dispatch({ type: SET_JUGADORES, players: loadedJugadores });
} catch (err) {
throw err;
}
};
}
export const fetchEstadistica = player_id => {
return async (dispatch) => {
//any async code here!!!
try {
const response = await fetch(
`https://api-football-v1.p.rapidapi.com/v2/players/player/${player_id}.json`,
{
method: 'GET',
headers: {
'x-rapidapi-key': Here goes my API KEY,
'x-rapidapi-host': 'api-football-v1.p.rapidapi.com',
'useQueryString': 'true'
}
}
);
if (!response.ok) {
throw new Error('Algo salio Mal!');
}
const resData = await response.json();
const loadesApiResult = [];
console.log('***Impresion desde la accion***');
console.log(resData);
console.log('***Fin de Impresionc***');
//Arrays de la Estadistica del Jugador
const loadedEstadistica = [];
const loadedCards = [];
const loadedGoals = [];
const loadedGames = [];
for (const key in resData) {
loadesApiResult.push(
new ResultadoEstadistica(
resData[key].results,
resData[key].players
)
);
}
const apiData = loadesApiResult.players;
for (const key in apiData) {
loadedEstadistica.push(
new PlayerEstadistica(
apiData[key].player_id,
apiData[key].player_name,
apiData[key].firstname,
apiData[key].lastname,
apiData[key].number,
apiData[key].position,
apiData[key].age,
apiData[key].birth_date,
apiData[key].birth_place,
apiData[key].birth_country,
apiData[key].nationality,
apiData[key].height,
apiData[key].weight,
apiData[key].injured,
apiData[key].rating,
apiData[key].team_id,
apiData[key].team_name,
apiData[key].league_id,
apiData[key].league,
apiData[key].season,
apiData[key].captain,
apiData[key].shots,
apiData[key].goals,
apiData[key].passes,
apiData[key].duels,
apiData[key].dribbles,
apiData[key].fouls,
apiData[key].cards,
apiData[key].penalty,
apiData[key].games,
apiData[key].substitutes,
)
);
}
const playerDataGames = loadedEstadistica.games;
for (const key in playerDataGames) {
loadedGames.push(
new Games(
playerDataGames[key].apperences,
playerDataGames[key].minutes_played,
playerDataGames[key].lineups
)
);
};
const playerDataGoals = loadedEstadistica.goals;
for (const key in playerDataGoals) {
loadedGoals.push(
new Goals(
playerDataGoals[key].total,
playerDataGoals[key].conceded,
playerDataGoals[key].assists,
playerDataGoals[key].saves
)
);
};
const playerDataCards = loadedEstadistica.cards;
for (const key in playerDataCards) {
loadedCards.push(
new Cards(
playerDataCards[key].yellow,
playerDataCards[key].yellowred,
playerDataCards[key].red
)
);
};
dispatch({ type: SET_ESTADISTICA, estadistica: loadesApiResult, goles: loadedGoals, juegos: loadedGames, tarjetas: loadedCards });
} catch (err) {
throw err;
}
};
};```
Finally this is my Redux Reducer just incase:
import { SET_JUGADORES, SET_ESTADISTICA } from "../actions/jugadores";
const initialState = {
availablePlayers: [],
estadistica: [],
playerGoals: [],
playerCards: [],
playerGames: [],
}
export default (state = initialState, action) => {
switch (action.type) {
case SET_JUGADORES:
return {
...state,
availablePlayers: action.players,
};
case SET_ESTADISTICA:
return{
...state,
estadistica: estadistica,
playerGoals: action.goles,
playerCards: action.tarjetas,
playerGames: action.juegos
};
}
return state;
};
Sorry for the Format but is giving me some issues; Any Ideas what my Be the Problem?
Thank you.
there are a few issues with your screen code, so i recommend simplifying the logic to make sure it works before adding anything thing else.
replace this:
const loadEstad = useCallback (async (param) => {
setError(null);
setIsRefreshing(true);
try {
await dispatch(userActions.fetchEstadistica(param));
} catch (err){
setError(err.message);
}
setIsRefreshing(false);
}, [dispatch, setIsLoading, setError]);
useEffect(() => {
setIsLoading(true);
loadEstad(estadId).then(() => {
setIsLoading(false);
});
}, [dispatch, loadEstad]);
console.log(estadId);
console.log(goles);
with this:
useEffect(()=>{
if (estadId) dispatch(userActions.fetchEstadistica(estadId));
},[estadId]);
Assuming your reducer/action code is correct, then this should call the api everytime the params estadId changes. The loading/refresh should be set in the reducer instead on the screen component.
a few things to keep in mind:
Don't await for dispatch.
console.log on state variables outside of promise resolving code block won't work.
This below won't work. instead, you should set the loading variable as a redux variable that gets updated after the API comes back with data.
loadEstad(estadId).then(() => {
setIsLoading(false);
});
I have the following code:
const { state, updateParentName, updateBabyName } = useContext(AuthContext);
const [isParentInputVisible, setParentInputVisible] = useState(false);
const toggleParentInput = () => {
setParentInputVisible(!isParentInputVisible);
};
<View style={styles.headerContainer}>
<Text style={styles.header}>Hello, </Text>
{isParentInputVisible
? (<TouchableOpacity onPress={toggleParentInput}>
<Text style={styles.headerLink}>{state.parentName}</Text>
</TouchableOpacity>)
: (<TextInput
value={state.parentName}
style={styles.parentInput}
onChangeText={(value) => {updateParentName(value);}}
onSubmitEditing={toggleParentInput}
/>)}
</View>;
And, as you can see, I use a Context to store my parentName value. I also set this var to parent as my default value.
Now, the problem that I am having and, that is driving me crazy, is I can't change the value in this Input field to anything. When I try to enter a new value there is returns undefined. When I remove onChangeText prop from that input for the sake of testing, it doesn't allow me to change the default value.
Can anyone point me to what should I do to fix it? I couldn't find anything useful that would help me.
UPDATE:
AuthContext file:
import createDataContext from './createDataContext';
const authReducer = (state, action) => {
switch (action.type) {
case 'update_parent_name':
return { ...state, parentName: action.payload };
default:
return state;
}
};
const updateParentName = (dispatch) => ({ parentName }) => {
dispatch({ type: 'update_parent_name', payload: parentName });
};
export const { Provider, Context } = createDataContext(
authReducer,
{
updateParentName,
},
{
parentName: 'parent',
}
);
and, just in case createDataContext code:
import React, { useReducer } from 'react';
export default (reducer, actions, defaultValue) => {
const Context = React.createContext(defaultValue);
const Provider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{ state, ...boundActions }}>
{children}
</Context.Provider>
);
};
return { Context, Provider };
};
The error is you've taken object as an argument in this function updateParentName but while calling the function through prop onChangeText you've passed the value as is without enclosing it in an object!
const updateParentName = (dispatch) => ({ parentName }) => {
dispatch({ type: 'update_parent_name', payload: parentName });
};
onChangeText={(value) => {updateParentName(value);}} //passed only value not in an object!
Just change the onChangeText prop as such
onChangeText={(value) => {updateParentName({parentName:value});}}
I have a screen with jobs that I fetch from an API. In my screen, I want to show a message until the jobs are fetched, then display them on the screen. I am triggering the jobs fetch in componentDidMount(), then trying to display them in RenderJobs(). The problem is that props.isLoading is undefined for whatever reason.
I am using hardcoded values for the API call at the moment. Once I get the data to display properly, I'll change this.
Here is my JobsComponent:
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {fetchJobs} from '../redux/ActionCreators';
const mapStateToProps = state => {
return {
jobs: state.jobs
}
}
const mapDispatchToProps = dispatch => ({
fetchJobs: (address, jobTitle) => dispatch(fetchJobs(address, jobTitle))
});
function RenderJobs(props) {
console.log('In RenderJobs, props is: ' + props.jobsData + ' / ' + props.isLoading);
const renderJobItem = ({item, index}) => {
return (
//UI view to show data
);
}
if (props.isLoading) {
return (
<View>
<Text style={{fontSize: 30, color: colors.white}}>The data is loading...</Text>
</View>
);
}
else if (props.errMess) {
return(
<View>
<Text style={{fontSize: 30, color: colors.white}}>{props.errMess} </Text>
</View>
);
}
else {
return (
//UI view to show data
);
}
}
class Jobs extends Component {
componentDidMount() {
this.props.fetchJobs([26.1410638, 44.4346588], "Developer");
}
render() {
return(
<ScrollView contentContainerStyle={styles.bkg}>
<RenderJobs
jobsData={this.props.jobs}
isLoading={this.props.jobs.isLoading}
errMess={this.props.jobs.errMess}
/>
</ScrollView>
)
}
}
This is my reducer:
import * as ActionTypes from '../ActionTypes';
export const jobs = (state = { isLoading: true,
errMess: null,
jobs:[]}, action) => {
switch (action.type) {
case ActionTypes.GET_JOBS:
return {...state, isLoading: false, errMess: null, jobs: action.payload};
case ActionTypes.JOBS_LOADING:
return {...state, isLoading: true, errMess: null, jobs: []}
case ActionTypes.JOBS_FAILED:
return {...state, isLoading: false, errMess: action.payload};
default:
return state;
}
};
And this is the action creator:
export const fetchJobs = (address, job) => async (dispatch) => {
dispatch(jobsLoading());
var obj = {"origin": [26.1410638, 44.4346588], "job_details": ["Developer"]};
//fetch the data
.then(response => response.json())
.then(jobs => dispatch(addJobs(jobs)))
.catch(error => dispatch(jobsFailed(error.message)));
};
export const addJobs = (jobs) => ({
type: ActionTypes.GET_JOBS,
payload: jobs
});
export const jobsLoading = () => ({
type: ActionTypes.JOBS_LOADING
});
export const jobsFailed = (errmess) => ({
type: ActionTypes.JOBS_FAILED,
payload: errmess
});
I am expecting for 2 things to happen.
In the RenderJobs() function, I am counting on props.isLoading to give me the loading state. However, it is undefined. I can see in the logs that the JOBS_LOADING action is dispatched, and that the jobs data is correctly fetched.
Once the jobs data is fetched, I expect it to be displayed in the UI. However, this is not the case - I just see a blank screen.
Any help will be greatly appreciated!
Looks like you have forgotten to add isLoading in your mapStateToProps.
isLoading will be undefined on first render as you have not defined any default value. You could provide default value using default props.
Jobs.defaultProps = {
jobs: {
isLoading: false
}
}
I found out what the issue was. In my configureStore.js, I had forgotten to add the jobs reducer to the store. Thanks to everyone for your answers!
import {jobTitles} from './reducers/jobTitles';
import {jobs} from './reducers/jobs';
import {persistStore, persistCombineReducers} from 'redux-persist';
import storage from 'redux-persist/es/storage';
export const ConfigureStore = () => {
const config = {
key: 'root',
storage,
debug: true
};
const store = createStore(
persistCombineReducers(config, {
jobTitles,
jobs //I added this line and it fixed the problem!
}),
applyMiddleware(thunk, logger)
);
const persistor = persistStore(store);
return {persistor, store};
}
I have a react native app using redux and immutable js. When i dispatch an action from my main screen, it goes through my actions, to my reducer and then back to my container, however, the view doesn't update and componentWillReceieveProps is never called. Furthermore, the main screen is a list whose items are sub components Item. Here's the relevant code for the issue, if you want to see more let me know.
Render the row with the data:
renderRow(rowData) {
return (
<Item item={ rowData } likePostEvent={this.props.likePostEvent} user={ this.props.user } removable={ this.props.connected } />
)
}
The part of Item.js which dispatches an action, and shows the result:
<View style={{flex: 1, justifyContent:'center', alignItems: 'center'}}>
<TouchableOpacity onPress={ this.changeStatus.bind(this, "up") }>
<Image source={require('../img/up-arrow.png')} style={s.upDownArrow} />
</TouchableOpacity>
<Text style={[s.cardText,{fontSize:16,padding:2}]}>
{ this.props.item.starCount }
</Text>
<TouchableOpacity onPress={ this.changeStatus.bind(this, "down") }>
<Image source={require('../img/up-arrow.png')} style={[s.upDownArrow,{transform: [{rotate: '180deg'}]}]} />
</TouchableOpacity>
</View>
The action dispatched goes to firebase, which has an onChange handler that dispatches another action.
The reducer:
const initialState = Map({
onlineList: [],
offlineList: [],
filteredItems: [],
connectionChecked: false,
user: ''
})
...
...
case ITEM_CHANGED:
list = state.get('onlineList')
if(state.get('onlineList').filter((e) => e.id == action.item.id).length > 0){
let index = state.get('onlineList').findIndex(item => item.id === action.item.id);
list[index] = action.item
list = list.sort((a, b) => b.time_posted - a.time_posted)
}
return state.set('onlineList', list)
.set('offlineList', list)
The container:
function mapStateToProps(state) {
return {
onlineItems: state.items.get('onlineList'),
offlineItems: state.items.get('offlineList'),
filteredItems: state.items.get('filteredItems'),
connectionChecked: state.items.get('connectionChecked'),
connected: state.items.get('connected'),
user: state.login.user
}
}
Where I connect the onChange:
export function getInitialState(closure_list) {
itemsRef.on('child_removed', (snapshot) => {
closure_list.removeItem(snapshot.val().id)
})
itemsRef.on('child_added', (snapshot) => {
closure_list.addItem(snapshot.val())
})
itemsRef.on('child_changed', (snapshot) => {
closure_list.itemChanged(snapshot.val())
})
connectedRef.on('value', snap => {
if (snap.val() === true) {
closure_list.goOnline()
} else {
closure_list.goOffline()
}
})
return {
type: GET_INITIAL_STATE,
connected: true
}
}
Calling get initial state:
this.props.getInitialState({
addItem: this.props.addItem,
removeItem: this.props.removeItem,
goOnline: this.props.goOnline,
goOffline: this.props.goOffline,
itemChanged: this.props.itemChanged
})
Any suggestions are welcome, thanks so much!
The source of your issue could be with the call to Firebase. If it is an asynchronous call, it's return callback might not be returning something that can be consumed by your action.
Do you know if it is returning a Promise? If that is the case, middleware exists that handle such calls and stops the calling of an action until a correct response is received. One such middleware is Redux-Promise.
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore,combineReducers } from 'redux' //Redux.createStore
import { Provider,connect } from 'react-redux';
//Функція яка змінює store
const hello = (state= {message:'none'}, action) => {
switch (action.type) {
case 'HELLO':
return Object.assign({}, state, {message:"hello world"});
break
case 'buy':
return Object.assign({}, state, {message:"buy"});
break;
case 'DELETE':
return Object.assign({}, state, {message:"none"});
break;
default :
return state;
}
};
const price = (state= {value:0}, action) => {
switch (action.type) {
case 'HELLO':
return Object.assign({}, state, {value: state.value + 1 });
break;
default :
return Object.assign({}, state, {value:0});
}
};
const myApp = combineReducers({
hello,price
});
//створюємо store
let store = createStore(myApp);
let unsubscribe = store.subscribe(() => console.log(store.getState()))
//VIEW
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<p>value: {this.props.price}</p>
<a href="#" onClick={this.props.onClick}>click</a><b>{this.props.message}</b>
</div>
)
}
}
//mapStateToProps() для чтения состояния и mapDispatchToProps() для передачи события
const mapStateToProps = (state, ownProps) => {
return {
message: state.hello.message,
price: state.price.value
}
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
var items= ['HELLO','buy','DELETE','error']
var item = items[Math.floor(Math.random()*items.length)];
dispatch({ type: item })
}
}
}
const ConnectedApp = connect(
mapStateToProps,
mapDispatchToProps
)(App);
ReactDOM.render(
<Provider store={store}>
<ConnectedApp />
</Provider>,
document.getElementById('app')
);