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
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});
});
}
...
}
In React itself, I have the function getTodos(), in which it calls another functiongetTodo(). It passes res.data[0].id to the getTodo() function.
React
Demo here: https://stackblitz.com/edit/react-qvsjrz
Code below:
class App extends Component {
constructor() {
super();
this.state = {
todos: [],
todo: {}
};
}
componentDidMount() {
this.getTodos()
}
getTodos = () => {
axios({
url: 'https://jsonplaceholder.typicode.com/todos',
method: 'GET'
})
.then(res => {
this.setState({
todos: res.data,
}, () => this.getTodo(res.data[0].id))
})
.catch(error => {
console.log(error);
});
};
getTodo = (todoId) => {
axios({
url: `https://jsonplaceholder.typicode.com/todos/${todoId}`,
method: 'GET'
})
.then(res => {
this.setState({
todo: res.data
})
})
.catch(error => {
console.log(error);
})
}
render() {
console.log(this.state.todo);
return (
<div>
</div>
);
}
}
The above code tries to convert to react + redux.
React + redux
In actions, I declared two functions getTodo andgetTodos. Could someone advise me on how to call the getTodo function in thegetTodos function by passing the getTodo id function?
Demo here: https://stackblitz.com/edit/react-ewpquh?file=actions%2Findex.js
actions
import axios from 'axios';
export const GET_TODOS = 'GET_TODOS';
export const FETCH_SUCCESS = 'FETCH_SUCCESS';
export const FETCH_FAILURE = 'FETCH_FAILURE';
export const getTodos = () =>
dispatch => {
return axios({
url: 'https://jsonplaceholder.typicode.com/todos',
method: 'GET',
})
.then(({data})=> {
console.log(data);
dispatch({type: GET_TODOS, payload:{
data
}});
})
.catch(error => {
console.log(error);
dispatch({type: FETCH_FAILURE})
});
};
export const getTodo = () =>
dispatch => {
return axios({
url: 'https://jsonplaceholder.typicode.com/todos',
method: 'GET',
})
.then(({data})=> {
console.log(data);
dispatch({type: GET_TODOS, payload:{
data
}});
})
.catch(error => {
console.log(error);
dispatch({type: FETCH_FAILURE})
});
};
Todos
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {getTodos} from '../.././actions';
class Todos extends Component {
componentDidMount() {
this.props.getTodos();
}
render() {
return (
<ul>
{this.props.todos.map(todo => {
return <li key={todo.id}>
{todo.title}
</li>
})}
</ul>
);
}
}
const mapStateToProps = state => {
console.log(state.todos);
const { todos } = state;
return {
todos
};
};
const mapDispatchToProps = dispatch => ({
getTodos: () => dispatch(getTodos())
});
export default connect(mapStateToProps, mapDispatchToProps)(Todos);
reducers
import {GET_TODOS} from '../../actions';
const initialState = {
todos: []
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'GET_TODOS':
return {
...state,
todos: action.payload.data
};
default:
return state;
}
};
export default rootReducer;
store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
Instead of over-complicating your actions, you should have separate action types for different APIs.
GET_TODOS - For /todos API
GET_TO - For /todos/ API
To add getTodo method with ID, this is how I solved it -
For each li tag, add an onClick that calls your getTodo API. (This is done as an example for the sake of adding getTodo in the workflow.
return <li key={todo.id} onClick={() => this.handleClick(todo.id)}>
Add handleClick which calls getTodo method from props.
First add getTodo in your components mapDispatchToProps:
import { getTodo, getTodos} from '../.././actions';
const mapDispatchToProps = dispatch => ({
getTodos: () => dispatch(getTodos()),
getTodo: id => dispatch(getTodo(id))
});
Add handleClick -
handleClick = id => {
this.props.getTodo(id).then(() => {
console.log(`You Clicked: ${JSON.stringify(this.props.todo)}`)
})
}
Update your getTodo action to take ID as input:
NOTE: The added GET_TODO type
export const getTodo = (id) => dispatch => {
return axios({
url: `https://jsonplaceholder.typicode.com/todos/${id}`,
method: 'GET',
})
.then(({data})=> {
// console.log(data);
dispatch({type: GET_TODO, payload: data});
})
.catch(error => {
console.log(error);
dispatch({type: FETCH_FAILURE})
});
};
Separate out your reducers into todos and todo and use combineReducers from redux package -
const todos = (state = [], action) => {
const { type, payload } = action;
switch(type) {
case 'GET_TODOS':
return payload;
default:
return state;
}
}
const todo = (state = {}, action) => {
const { type, payload } = action;
switch(type) {
case 'GET_TODO':
return payload;
default:
return state;
}
}
const rootReducer = combineReducers({todos, todo});
Run the app and click on any item in the todo list. Console log for the clicked todo item is shown when API response for that ID is fetched.
The live sandbox is available here - https://stackblitz.com/edit/react-ndkasm
In my store.js i have the following code:
import { createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk'
const reducer = (state, action) => {
console.log(action.type)
if (action.type === 'LOAD_USERS') {
return {
...state,
users: action.users['users']
}
} else if (action.type === 'LOAD_CHATROOMS') {
return {
...state,
chatRooms: action.chatRooms['chatRooms']
}
}
return state;
}
export default createStore(reducer, {users:[], chatRooms:[]}, applyMiddleware(thunk));
the code inside the action.type === 'LOAD_CHATROOMS' is never accessed for some reason, this is the action file where i set the action type for the reducer:
import axios from 'axios'
axios.defaults.withCredentials = true
const loadUsers = () => {
return dispatch => {
return axios.get('http://localhost:3000/session/new.json')
.then(response => {
dispatch({
type: 'LOAD_USERS',
users: response.data
});
});
};
};
const logIn = user => {
return axios.post('http://localhost:3000/session', {
user_id: user.id
})
.then(response => {
//TODO do something more relevant
console.log('loged in');
});
};
const loadChatRooms = () => {
return dispatch => {
return axios.get('http://localhost:3000/session/new.json')
.then(response => {
dispatch({
type: 'LOAD_CHATROOMS',
chatRooms: response.data
});
});
};
};
const enterChatRoom = chatrom => {
};
export { loadUsers, logIn, enterChatRoom, loadChatRooms};
The 'Load methods' get the data that i use to populate both components (one for users list and the other one for chatrooms list ), both components are called at the same level in the app.js file.
Basically the output that i'm getting is the first component (users) as expected with the correct list, and the chatrooms component is also rendered but the data is not loaded (since it's corresponding reducer block is not accessed).
Thanks a lot for reading :)
I've been making an app that is using the Google Sheet API and React/Redux.
If I hit the API from the component itself it works but I'm having an issue when it comes to fetch data through Redux.
This is code
Action creator:
export function fetchList() {
let data = null;
gapi.client.sheets.spreadsheets.values.get({
spreadsheetId: FULL_LIST_ID,
range: RANGE
}).then((response) => {
data = response.result.values;
}, (response) => {
throw response.result.error.message;
});
return {
type: FETCH_LIST,
payload: data
}
}
Reducer:
export default function(state = INITIAL_STATE, action = {} ) {
switch (action.type) {
case FETCH_LIST:
return { ...state, list: action.payload };
default:
return state;
}
}
Component:
import React from 'react';
import { connect } from 'react-redux';
import { fetchList } from '../../actions/index.jsx';
export class DropdownList extends React.Component {
constructor(props) {
super(props);
this.state = { res: null }
// this._fetchList = this._fetchList.bind(this);
}
componentWillMount() {
// this should fetch the data from Redux
this.props.fetchList();
// so that when
console.log(this.props);
// I should see the values attached to the payload
// instead this is fetching the data from the API hit here
this._fetchList();
}
// Here I'm hitting the API from the component
_fetchList() {
gapi.client.sheets.spreadsheets.values.get({
spreadsheetId: FULL_LIST_ID,
range: ['LIST!A1:B']
}).then((response) => {
this.setState({ res: response.result.values });
}, (response) => {
throw response.result.error.message;
});
}
_renderList() {
// this uses the values fetched locally
// return this.state.res.map((val, index) => {});
}
render() {
if (!this.state.res) {
return <div>Loading...</div>;
}
return (
<div>
{this._renderList()}
</div>
);
}
}
function mapStateToProps(state) {
return { list: state.list }
}
export default connect(mapStateToProps, { fetchList })(DropdownList);
Does anybody can help me out?
Thanks
OK, solved!
It was an issue of sync so I needed to use Redux-Thunk as a middleware in my Action Creator.
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.