Right now I am mapping over an array with an endpoint to my API. From there I am taking every link and calling a get request on each thing I map over. My issue is that I am not able to save everything into my redux state. I have tried using concat and push to take everything and put it all in one array in my redux state.
MomentContent.js:
componentDidMount () {
this.props.photos.map(photo => {
this.props.fetchPhoto(this.props.token, photo)}
)
}
index.js (actions):
export const fetchPhoto = (token, photo) => dispatch => {
console.log('right token')
console.log(token);
fetch(photo, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Token ${token}`,
}
})
.then(res => res.json())
.then(parsedRes => {
console.log('photo data')
console.log(parsedRes)
dispatch(getPhoto(parsedRes))
})
}
export const getPhoto = (photo) => {
console.log('RES')
console.log(photo)
return {
type: GET_PHOTO,
photo: photo
}
}
When I use concat (reducer):
import {
GET_PHOTO
} from '../actions';
const initialState = {
photo: []
}
const photoReducer = (state = initialState, action) => {
switch(action.type) {
case GET_PHOTO:
return {
...state,
photo: initialState.photo.concat([action.photo])
}
default:
return state;
}
}
export default photoReducer
When I use push (reducer):
import {
GET_PHOTO
} from '../actions';
const initialState = {
photo: []
}
const photoReducer = (state = initialState, action) => {
switch(action.type) {
case GET_PHOTO:
return {
...state,
photo: initialState.photo.push([action.photo])
}
default:
return state;
}
}
export default photoReducer
UPDATE (another issue):
I was able to get it to work with :
return {
...state,
photo: [...state.photo, action.photo]
}
The issue now is that every time I refresh the same data is pushed again, so everything multiplies. Is there a way to fix this?
You need to merge your updatedState and not initialState to the reducer in order to update
Either using concat:
return {
...state,
photo: state.photo.concat([action.photo])
}
or using spread operator
return {
...state,
photo: [...state.photo, action.photo]
}
the push does not work correctly in redux, the ideal is to use the spread operator to concatenate the arrays
return {
... state,
photo: [... initialState.photo, action.photo]
}
If action.photo is an array, no need to wrap it with additional [].
If you want the newly fetched photo array to combined with existing photo array in the Redux state, use state.photo.push instead of initialState.photo.push.
case GET_PHOTO:
return {
...state,
photo: state.photo.push(action.photo)
}
Javascript push method on array return you the new size of the array and hence it won't work correctly.
what you need is to use concat or spread-syntax
case GET_PHOTO:
return {
...state,
photo: initialState.photo.concat([action.photo])
}
or
case GET_PHOTO:
return {
...state,
photo: [...initialState.photo, action.photo]
}
Related
I am like in a strange problem. The problem is that I am trying to make an API hit (in service file) which in turn provides some data (it is working), this data is to be updated in my reducer1.js and then returned. Now, my issue is though the value is coming in reducer file, but is not returned, so in turn, state is not changed, and in turn my end component is not rerendered.
Now, when my service file is successfully hitting and then returning data to my reducer1.js, why in the world the updated-state is not returned by "GET_List" action type? Can someone see any problem?
index.js (service file)
const global = {
getActressList: async function(){
const response = await fetch("http://localhost:2000/api/actressList");
const data = await response.json();
return data;
}
}
export default global;
reducer1.js
import global from '../../services/index';
const initialState = {
data: [
{
id: 1,
name: "Aishwarya Rai",
src: "/assets/img/aishwarya.png"
}
]
};
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
const data = global.getActressList();
data.then((res)=> {
return {
...state,
data: res
}
})
}
default:
return state;
}
}
export default reducer1;
Result:
You are returning from a promise not from a reducer function:
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
const data = global.getActressList();
data.then((res) => {
// here you are returning from a promise not from a reducer function
return {
...state,
data: res,
};
});
}
default:
return state;
}
}
The code in reducer should be sync like this:
function reducer1(state = initialState, action) {
switch (action.type) {
case "GET_LIST": {
return {
...state,
data: action.payload,
};
}
default:
return state;
}
}
And your data fetching should be moved to component effect like this:
function YourComponent() {
const dispatch = useDispatch();
const data = useSelector(state => state.data)
useEffect(() => {
const data = global.getActressList();
data.then((res) => {
dispatch({type: 'GET_LIST', payload: res});
});
}, [])
...
}
EDIT
If you use class components the fetching logic should be placed in componentDidMount lifecycle hook like this:
class YourComponent extends Component {
state = { data: [] };
componentDidMount() {
const data = global.getActressList();
data.then((res) => {
dispatchYourAction({type: 'GET_LIST', payload: res});
});
}
...
}
If I fetch this array of restos with redux:
[{
res_id: Int,
res_name: String,
res_category: String,
res_category_id: Int,
city_id: Int
}]
My action looks something like this:
export const getrestos = () => {
const resData = await response.json();
dispatch({
type: GET_RESTOS,
payload: resData
});
};
};
export const setFilters = filterSettings => {
console.log(filterSettings);
return { type: SET_FILTERS, filters: filterSettings };
};
And this is my reducer:
import { GET_RESTOS, SET_FILTERS } from '../actions/restos';
const initialState = {
restoList: [],
filteredRestos: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_RESTOS:
return {
restoList: action.payload
}
case SET_FILTERS:
const appliedFilters = action.filters;
const updatedFilteredRestos = state.restoList.filter(resto => {
if (appliedFilters.cityID || resto.city_id) {
resto => resto.city_id.indexOf(cityID) >= 0
return { ...state, filteredRestos: updatedFilteredRestos };
}
});
return { ...state, filteredRestos: updatedFilteredRestos };
default:
return state;
}
};
I have touchable categorys in a page, and when i touch one i want to fetch the corresponding restos for that category and show them in a flatlist. Apart from that i want to have a search bar that when I type I want to show restos by res_name and/or by res_category.
Ive tried to create selectors, but I dont understand how, i dont need an specific approach, but the most clean or efficient as possible.
Thanks in advance if anyone can give me a hint or solution!
EDIT
The problem is im getting undefined in updatedFilteredRestos.
Your reducers should be clean, dumb and all they do should be returning objects. This makes your components more testable and errors easier to catch. In my opinion, this is a perfect use-case for reselect. Here's a medium article: https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c But the true beauty of reselect is that it will memoize for you, i.e. if your states don't change, it uses a cached version of the data.
Anyway, you should clean up your restoReducer to something to this effect.
import { GET_RESTOS, SET_FILTERS } = "../actions/restos";
const initialState = {
restoList: [],
filteredRestos: []
};
const restoReducer = (state = initialState, action) => {
switch(action.type) {
case GET_RESTOS:
return { ...state, restoList: action.payload };
case SET_FILTERS:
return { ...state, filteredRestos: action.payload };
default:
return state;
}
}
Then write your filtered resto selector:
// ../selectors/restos
import { createSelector } from "reselect";
// First, get your redux states
const getRestos = (state) => state.restos.restoList;
const getFilteredRestos = (state) => state.restos.filteredRestos;
// Next, create selectors
export const getFilteredRestoList = createSelector(
[getRestos, getFilteredRestos],
(restoList, filteredRestos) => {
// need to check for non-empty filters
// if it is, simply return the unfiltered `restoList`
if(!Array.isArray(filteredRestos) || !filteredRestos.length)
return restoList || [];
// If you do have valid filters, return filtered logic
return restoList.filter(r => filteredRestos.some(f => f.cityID === r.city_id));
);
Then, use this selector in your components:
// ../components/my-app
import { getFilteredRestoList } from "../selectors/restos";
// hook it up to your `mapStateToProps` as you would a normal state
// except this time, it's a special selector
const mapStateToProps = (state, ownProps) => {
restoList: state.restos.restoList,
filteredRestos: state.restos.filteredRestos,
filteredRestoList: getFilteredRestoList(state) //<-- this is your selector
}
Then inside your component, just reference it: this.props.filteredRestoList.
I've run into an odd issue, where my redux store seems to be returning a duplicate of a different value? (Still learning terms so sorry if I mixed them up!)
I have 2 states. Users, and Added. I want to show to lists, one using the data from each one of them. currently, fetchUsers works fine, but fetchAdded shows Users for an unknown reason so both lists show the same data.
If I switch fetchUsers to use refAdded then it shows Added, so now it only shows the added array in both lists. I figured that means the actual calls are working cause it can get the data from Firebase, but I don't know why this would happen.
FetchUsers which gets a list of users from firebase looks like this:
export function fetchUsers() {
return (dispatch) => {
refUsers.on('value', snapshot => {
dispatch({
type: 'FETCH_USER',
payload: snapshot.val()
});
});
}
}
FetchAdded looks like this:
export function fetchAdded() {
return (dispatch) => {
refAdded.on('value', snapshot => {
dispatch({
type: 'FETCH_ADDED',
payload: snapshot.val()
});
});
}
}
The reducers look like this:
export default function(state = [], action) {
switch (action.type) {
case 'FETCH_USER':
return [action.payload];
case 'ADDED_USER':
return [action.payload, ...state];
case 'MOVE_USER':
const newState = [...state];
newState.splice(action.payload.index, 1);
return newState;
case 'MOVE_ITEM':
return [action.payload.user, ...state];
default:
return state
}
}
and fetch Added is:
export default function(state = [], action) {
switch (action.type) {
case 'FETCH_ADDED':
return [action.payload];
case 'MOVE_ITEM':
const newState = [...state];
newState.splice(action.payload.index, 1);
return newState;
case 'MOVE_USER':
return [action.payload.user, ...state]
default:
return state
}
}
I combine them both here:
const rootReducer = combineReducers({
users: UserReducer,
added: AddedReducer
});
and my firebase client exporting looks like this:
firebase.initializeApp(config);
export const refUsers = firebase.database().ref("users")
export const refAdded = firebase.database().ref("added")
export const auth = firebase.auth
export const provider = new firebase.auth.GoogleAuthProvider();
In my actual page where I display the 2 lists, this is what I have:
function mapStateToProps(state) {
return {
users: state.users,
added: state.added
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ addUser, moveUser, moveItem, fetchUsers, fetchAdded }, dispatch);
}
I'm new to Redux and I think I'm starting to understand how it all works, but I'm having initial problems getting data into the Store.
I believe I'm close, but there's just something that I'm not getting. Any help is appreciated!
The reason I need this to work is because I have other components that will work with the same data, so I figured it's best to keep the data in the Redux Store. If there are other ways to solve this, please enlighten me.
Action:
import fetch from "isomorphic-fetch";
export const LOAD_DATA = "LOAD_DATA";
function getApiUrl() {
return `${window.appDefaultState.url.baseUrl}/api`;
}
export function loadStoresData() {
return function(dispatch) {
dispatch({
type: LOAD_DATA,
stores: data
});
fetch(
getApiUrl(),
{
method: "post",
credentials: 'same-origin',
body: JSON.stringify({
form_key: window.appDefaultState.formKey,
"cms/stores": 1
})
}
)
.then(response => response.json())
.then(json => {
console.log("fetched data in actions")
let data = json["cms/stores"];
console.log(data);
dispatch({
type: LOAD_DATA,
stores: data
});
})
.catch(e => {
console.log(e)
});
}
}
function getSuccess(data) {
console.log("getSuccess worked")
return (
type: LOAD_DATA,
stores: data
)
}
Reducer:
import {
LOAD_DATA
} from "actions/storelist.js";
function initialState() {
return Object.assign({}, {
stores: {},
}, window.appDefaultState.storeList);
}
export default function storeList(state, action) {
if (!state) {
state = initialState();
}
switch (action.type) {
case LOAD_DATA:
return Object.assign({}, state, {
stores: action.data
});
break;
}
return state;
}
Component (relevant parts):
import { connect } from "react-redux";
import { loadStoresData } from "actions/storelist.js";
const actions = {
loadStoresData
}
const mapStateToProps = (state, ownProps) => {
return Object.assign({
stores: state.stores
}, ownProps);
};
export default connect(mapStateToProps, actions)(StorePage);
You dispatch data in stores field, so it should be
switch (action.type) {
case LOAD_DATA:
return Object.assign({}, state, {
stores: action.stores
});
}
Replace action.data with action.stores
I am still learning React-Redux. I understand how to retrieve simple JSON arrays. However, I am not sure how to call a nested object. I am trying to grab the title and am viewing this in the console:
Object
data
:
Object
data
:
Object
data
:
Object
after
:
"t3_5t0hy2"
before
:
null
children
:
Array[25]
0
:
Object
data
:
Object
title
:
"The Google Analytics Setup I Use on Every Site I Build (by Philip Walton)"
dataAction.js
import axios from 'axios';
export function fetchData(){
return function(dispatch){
axios.get("https://www.reddit.com/r/webdev/top/.json")
.then((response) => {
dispatch({ type: "FETCH_DATA_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_DATA_REJECTED", payload: err})
})
}
}
export function addData(id, text){
return {
type: 'ADD_DATA',
payload:{
id,
title,
},
}
}
export function updateData(id, text){
return {
type: 'UPDATE_DATA',
payload: {
id,
title,
},
}
}
export function deleteData(id){
return {
type: 'DELETE_DATA',
payload: id
}
}
Layout.js (component)
import React from "react"
import { connect } from "react-redux"
import { fetchUser } from "../actions/userActions"
import { fetchPartner } from "../actions/projectActions"
import { fetchData } from "../actions/dataActions"
#connect((store) => {
return {
user: store.user.user,
userFetched: store.user.fetched,
partner: store.partner.partner,
partnerFetched: store.partner.fetched,
data: store.data.data
};
})
export default class Layout extends React.Component {
componentWillMount() {
this.props.dispatch(fetchUser())
this.props.dispatch(fetchPartner())
this.props.dispatch(fetchData())
}
render() {
const { user, partner, data } = this.props;
//const mappedData = data.map(data => <li>{data.title}</li>)
return <div>
<h1>{user.name}{user.age}</h1>
<h1>{partner.title}</h1>
<ul>{data.title}</ul>
</div>
}
}
Reducer.js
export default function reducer(state={
data: {
data: {}
},
fetching: false,
fetched: false,
error: null,
}, action) {
switch(action.type){
case "FETCH_DATA":{
return {...state, fetching:true}
}
case "FETCH_DATA_REJECTED":{
return {...state, fetching: false, error: action.payload}
}
case "FETCH_DATA_FULFILLED":{
return {...state, fetching: false, fetched: true, data: action.payload}
}
case "ADD_DATA":{
return {...state, data: [...state.data, action.payload]}
}
case "UPDATE_DATA":{
const { id, title } = action.payload
const newData = [...state.data]
const dataToUpdate = newData.findIndex(data => data.id === id)
newData[dataToUpdate] = action.payload;
return {...state, data: newData}
}
case "DELETE_DATA":{
return {...state, data: state.data.filter(data => data.id !== action.payload)}
}
}
return state
}
When this issue is solved, the next step would be to iterate through the object, which I'm also not sure how to achieve.
As you are sending payload: response.data You can go further in the object structure and send the actual data in payload.
Once you send the payload you would need a reducer which will change the state. Follow this tutorial on how to create reducer.
http://blog.scottlogic.com/2016/05/19/redux-reducer-arrays.html
Then once the state is updated, you will have the code reading the values from state. Once the state change the React will automatically update or render and you can write your logic.