I am creating a React application and integrating Redux to it in order to manage the state and do network requests.
I followed the Todo tutorial and I am following the async example from the redux website, but I am stucked.
Here is my problem, I want, in my application, to fetch a user from a remote server. So the server send me a json array containing an object (maybe it's better if the server send me directly an object ? )
The json I obtain looks like that (I put only two fields but there are more in real) :
[{first_name: "Rick", "last_name": "Grimes"}]
Anyway I can fetch the data from the server but I can't inject user's data into my application, I hope you can help me but the most important is that I understand why it doesn't work.
Here are my several files :
I have two actions, one for the request and the other for the response:
actions/index.js
export const REQUEST_CONNECTED_USER = 'REQUEST_CONNECTED_USER';
export const RECEIVE_CONNECTED_USER = 'RECEIVE_CONNECTED_USER';
function requestConnectedUser(){
return {
type: REQUEST_CONNECTED_USER
}
}
function receiveConnectedUser(user){
return {
type: RECEIVE_CONNECTED_USER,
user:user,
receivedAt: Date.now()
}
}
export function fetchConnectedUser(){
return function(dispatch) {
dispatch(requestConnectedUser());
return fetch(`http://local.particeep.com:9000/fake-user`)
.then(response =>
response.json()
)
.then(json =>
dispatch(receiveConnectedUser(json))
)
}
}
reducer/index.js
import { REQUEST_CONNECTED_USER, RECEIVE_CONNECTED_USER } from '../actions
function connectedUser(state= {
}, action){
switch (action.type){
case REQUEST_CONNECTED_USER:
return Object.assign({}, state, {
isFetching: true
});
case RECEIVE_CONNECTED_USER:
return Object.assign({}, state, {
user: action.user,
isFetching: false
});
default:
return state;
}
}
And I have finally my container element, that I have called Profile.js
import React from 'react';
import { fetchConnectedUser } from '../actions';
import { connect } from 'react-redux';
class Profile extends React.Component{
constructor(props){
super(props);
}
componentDidMount() {
const { dispatch } = this.props;
dispatch(fetchConnectedUser());
}
render(){
const { user, isFetching} = this.props;
console.log("Props log :", this.props);
return (
<DashboardContent>
{isFetching &&
<div>
Is fetching
</div>
}
{!isFetching &&
<div>
Call component here and pass user data as props
</div>
}
</DashboardContent>
)
}
}
function mapStateToProps(state) {
const {isFetching, user: connectedUser } = connectedUser || { isFetching: true, user: []}
return {
isFetching,
user: state.connectedUser
}
}
export default connect(mapStateToProps)(Profile)
In the code above, I always have the Is Fetching paragraph being display, but even when I remove it, I cannot access to user data.
I think I have the beginning of something because when I console.log I can see my actions being dispatched and the data being added to the state, but I think I am missing something in the link communication with this data to the UI Component.
Am I on the good way or do I have a lot of things to refactor ? Any help would be very helpful !
Seeing as you are immediately fetching the data I allow myself to assume that isFetching may be true at beginning. Add an initial state to your reducer
state = { isFetching: true, user: null }
Then assuming you setup the reducer correctly:
function mapStateToProps(state) {
const {isFetching, user } = state.connectedUser
return {
isFetching,
user
}
}
Hope this works, feels clean-ish.
Related
I'm trying to map this array but the it keeps saying .includes is undefined. I assume maybe its because I've mapped is wrong? I'm not sure but I will list everything I have.
Here is what my api data looks like when I console.log(this.props.tournaments it from from my redux store:
console.log
You see the undefined in the first index of the array, I've used an Object.keys(tournaments).map(key => tournaments[key]) and .map (which you can see in the component snippet below) twice which when I console.log I get the correct results but I still get an error when its passes through the .filter function.
Here is what it looks like after I've formatted the array:
console.log after formatting
but I'm still getting an error...
This the error I'm getting:
Error message
Here is the component in question:
import React from 'react';
import { connect } from 'react-redux';
import { fetchTournaments } from '../actions/tournaments';
class Search extends React.PureComponent {
// State only needs to hold the current filter text value:
componentDidMount() {
this.props.fetchTournaments();
}
state = {
filterText: ''
};
handleChange = event => {
this.setState({ filterText: event.target.value });
};
render() {
let tourns = this.props.tournaments.map(tore =>
tore.map(room => room.name)
);
console.log(tourns, 'here');
const filteredList = tourns.filter(item =>
item.name.includes(this.state.filterText)
);
return (
<React.Fragment>
<input onChange={this.handleChange} value={this.state.filterText} />
<ul>{filteredList.map(item => <li key={item.id}>{item.name}</li>)}</ul>
</React.Fragment>
);
}
}
function mapStateToProps({ tournaments }) {
return {
tournaments: Object.keys(tournaments).map(key => tournaments[key])
};
}
export default connect(mapStateToProps, {
fetchTournaments
})(Search);
My data is coming from redux like so:
Reducer:
import _ from 'lodash';
import {
FETCH_TOURNAMENT,
from '../actions/types';
export default (state = {}, action) => {
switch (action.type) {
case FETCH_TOURNAMENT:
return { ...state, [action.payload.id]: action.payload };
default:
return state;
}
};
action:
import {
FETCH_TOURNAMENT,
} from './types';
import { API_TOURNAMENTS_URL } from '../constants/api';
import axios from 'axios';
export const fetchTournament = id => async dispatch => {
const response = await axios.get(`http://localhost:4000/tournaments/${id}`);
dispatch({ type: FETCH_TOURNAMENT, payload: response.data });
};
I think you could treat you response.data before setting to state to reflect more how you will be handling your application. I would imagine that you would rather have an array of objects.
you could try passing to payload Object.values(response.data).flat() instead which would give you an array of of objects based on your response.
Edit
below is without implementing Object.values(response.data).flat(). if you follow the suggestion you shouldnt need to treat
about the issue at filter, item is an array containing name value at index 0. this is why you get undefined. this happens because of tore.map(room => room.name) returns an array.
if you change as below you may get your array of tourns:
let tourns = this.props.tournaments.flat();
but I would consider to treat your response which would avoid all these changes on component level. pick one which suits you better.
From this guide I found here:
https://daveceddia.com/where-fetch-data-redux/
I have a pretty standar reducer that handle data, loading and error:
import {
FETCH_PRODUCTS_BEGIN,
FETCH_PRODUCTS_SUCCESS,
FETCH_PRODUCTS_FAILURE
} from './productActions';
const initialState = {
items: [],
loading: false,
error: null
};
export default function productReducer(state = initialState, action) {
switch(action.type) {
case FETCH_PRODUCTS_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_PRODUCTS_SUCCESS:
return {
...state,
loading: false,
items: action.payload.products
};
case FETCH_PRODUCTS_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
items: []
};
default:
return state;
}
}
And then the component that call the action and draw based on those states:
import React from "react";
import { connect } from "react-redux";
import { fetchProducts } from "/productActions";
class ProductList extends React.Component {
componentDidMount() {
this.props.dispatch(fetchProducts());
}
render() {
const { error, loading, products } = this.props;
if (error) {
return <div>Error! {error.message}</div>;
}
if (loading) {
return <div>Loading...</div>;
}
return (
<ul>
{products.map(product =>
<li key={product.id}>{product.name}</li>
)}
</ul>
);
}
}
const mapStateToProps = state => ({
products: state.products.items,
loading: state.products.loading,
error: state.products.error
});
export default connect(mapStateToProps)(ProductList);
This works fine the very first time:
products is empty. So the first render will show the empty list. The second time(after the fetch is completed) products will have items.
Now, the problem is what happens if I get outside then component and then re-enter(for example, using react-router).
The very first time It will draw with the cached information in the redux-store. Then after the fetch I will redraw the new list.
Is there any way to avoid this every time?
i have thought a couple of "solutions" but I'm not soure if they will work/are good practices:
setting in the component state a value "fetchId" (example, generating a random UUID) and use it in the fetchProducts action. That value would be saved in the redux store and the compare the redux fetchId with the component. If they are the same, DRAW! If they are differente(the fetchId comes from a different call) I will not draw anything.
Cleaning up redux store calling an action in the componentWillUmount
Store the product I'd in Redux and only render the product if it matches the one the user has selected. If it doesn't match, fetch the requested product.
I've always used react-redux connect to configure props but I need to use a react Component to use lifecycle methods. I'm noticing that my props that I'm grabbing from the store seem to be static and they do not update as the store updates.
Code:
class AlertModal extends Component {
title
isOpen
message
componentDidMount() {
const { store } = this.context
const state = store.getState()
console.log('state', state)
console.log('store', store)
this.unsubscribe = store.subscribe(() => this.forceUpdate())
this.title = state.get('alertModal').get('alertModalTitle')
this.isOpen = state.get('alertModal').get('isAlertModalOpen')
this.message = state.get('alertModal').get('alertModalMessage')
this.forceUpdate()
}
componentWillUnmount() {
this.unsubscribe()
}
updateAlertModalMessage(message) {
this.context.store.dispatch(updateAlertModalMessage(message))
}
updateAlertModalTitle(title) {
this.context.store.dispatch(updateAlertModalTitle(title))
}
updateAlertModalIsOpen(isOpen) {
this.context.store.dispatch(updateAlertModalIsOpen(isOpen))
}
render() {
console.log('AlertModal rendered')
console.log('AlertModal open', this.isOpen) <======= stays true when in store it is false
return (
<View
How do I set up title, isOpen, and message so they reflect the store values at all times?
It should be something like this. In your Confirmation component:
const mapStateToProps = (state) => {
return { modalActive: state.confirmation.modalActive };
}
export default connect(mapStateToProps)(Confirmation);
In your reducer index file, is should be something like this:
const rootReducer = combineReducers({
confirmation: ConfirmationReducer
});
I believe you have your own reducer file called ConfirmationReducer here. It should be something like this.
import { ON_CONFIRM } from '../actions';
const INITIAL_STATE = {modalActive: true};
export default function(state = INITIAL_STATE, action) {
console.log(action);
switch (action.type) {
case ON_CONFIRM:
return { ...state, modalActive: action.payload };
}
return state;
}
Make sure you write your own action creator to create an action with the above type and relevant payload of boolean type.
Finally you should be able to access the property from the store inside your Confirmation component like this:
{this.props.modalActive}
You have not posted entire code, so it makes very difficult to give a solution to the exact scenario. Hope this helps. Happy coding!
For me the problem was that I was assigning this.props.myObject to a variable which wasn't deep cloned so I fixed it by using
let prev = Object.assign({}, this.props.searchData)
What I was doing
let prev = this.props.searchData
So I was disturbing the whole page.Seems quiet noob on my part.
this may help you
componentWillReceiveProps(nextProps) {
console.log();
this.setState({searchData : nextProps.searchData})
}
I've been working on a React app and have gotten to a point where I'll need Redux to handle some aspects of it.
After reading a bunch of tutorials, I'm fairly stuck on how to make my "smarter" components "dumber" and move functions into my actions and reducers.
So, for example, one aspect of the app is more of a to-do list style.
One of my classes starts like this:
export default class ItemList extends React.Component {
constructor() {
super();
this.state = { items: [],
completed: [],
};
this.addItem = this.addItem.bind(this);
this.completeItem = this.completeItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
}
addItem(e) {
var i = this.state.items;
i.push({
text: this._inputElement.value,
paused: false,
key: Date.now()
});
this.setState({ items: i });
e.preventDefault();
this._inputElement.value = '';
this._inputElement.focus();
}
completeItem(e) {
this.deleteItem(e);
var c = this.state.completed;
c.push({
text: e.target.parentNode.parentNode.getElementsByClassName('item-name')[0].innerHTML,
paused: false,
key: Date.now()
});
this.setState({ completed: c });
}
deleteItem(e) {
var i = this.state.items;
var result = i.filter(function(obj) {
return obj.text !== e.target.parentNode.parentNode.getElementsByClassName('item-name')[0].innerHTML;
});
this.setState({ items: result });
}
// ... more irrelevant code here ...
// there's a function called createTasks that renders individual items
render() {
var listItems = this.state.items.map(this.createTasks);
return <div className="item-list">
<form className="form" onSubmit={this.addItem}>
<input ref={(a) => this._inputElement = a}
placeholder="Add new item"
autoFocus />
<button type="submit"></button>
</form>
{listItems}
</div>;
}
}
So, as you can see, it's very logic-heavy. I've started adding Redux by adding a <Provider> in my index file, and made a basic reducers file that is fairly empty so far:
import { combineReducers } from 'redux';
const itemList = (state = {}, action) => {
};
// ... other irrelevant reducers
const rootReducer = combineReducers({
itemList,
// ...
});
export default rootReducer;
...and I've made an actions file that doesn't have much in it yet either.
I've been struggling to figure out:
Most actions I've seen examples of just return some kind of JSON, what do I return in the reducer that uses that JSON that my component can use?
How much of my component logic is reusable, or should I just forget it? What is the best way to go about this to reuse as much code as I've written as possible?
First of all you need to understand the overall picture of how redux works with react.
Before coming to that lets first understand what are smart components and dumb components.
Smart Components
All your code logic needs to be handled here
They are also called containers.
They interact with the store(aka state management) to update your components.
Dumb Components
They just read props from your containers and render you components
This is just the UI view and should not contain any logic.
All styling/html/css comes in your dumb components.
Here is an amazing piece of article which you can go through to understand smart and dumb components if you still have doubts.
Ok, now lets try understanding how redux works:-
Your smart components(aka containers) interact with your redux store
You fire actions from your containers.
Your actions call your apis
The result of your action updates the store through a reducer
You containers read the store through mapStateToProps function and as soon as value in store changes it updates your component.
Now lets consider your todo example
TodoListContainer.js
class TodoListContainer extends Component {
componentWillMount () {
// fire you action action
}
render () {
return (
<Todos todos=={this.props.todos} />
)
}
}
function mapStateToProps(state) {
const {todos} = state;
return {
todos;
}
}
export default connect(mapStateToProps)(TodoListContainer)
TodoList.js
class TodoList extends Component {
renderTodos() {
return this.props.todos.map((todo)=>{
return <Todo todo={todo} key={todo.id} />
})
}
render () {
return () {
if (this.props.todos.length === 0) {
return <div>No todos</div>
}
return (
<div>
{this.renderTodos()}
</div>
)
}
}
}
export default class TodoList
Todo.js
class Todo extends Component {
render () {
return (
<div>
<span>{this.props.todo.id}</span>
<span>{this.props.todo.name}</span>
</div>
)
}
}
Reducer
export default function todos(state={},action) {
switch (action.type) {
case 'RECEIVE_TODOS':
return Object.assign(state,action.todos);
}
}
action
function fetchTodos() {
return(dispatch) => {
axios.get({
//api details
})
.then((res)=>{
dispatch(receiveTodos(res.todos))
})
.catch((err)=>{
console.warn(err)
})
}
}
function receiveTodos(todos) {
return {
type: 'RECEIVE_TODOS',
todos
}
}
Now if you have read redux documentation you would see that actions return objects then how would i call my api there which returns a function instead of an object. For that I used redux thunk about which you can read here.
I gave you an example in which you can fetch todos. If you want to do other operations like deleteTodo, addTodo, modifyTodo then you can do that in appropriate components.
DeleteTodo - you can do in TodoListContainer.
AddingTodo - you can do in TodoListContainer.
Changing State(completed/Pending) - you can do in TodoListContainer.
ModifyingTodo - you can do in TodoContainer.
You can also check out here for a detailed example, but before that I would say just should go through basics of redux which you can find here
P.S: I wrote code on the fly so it might not work properly but it should work with little modification.
I am having server render on a demo react application of mine. Although it works if i refresh the page on a url to fetch the doctor like /doctor/:id if i am at /login and try to go to /doctor/123456 the doctor property is empty and (this.props.doctor.name.first) fails.
What is a good approach to use redux to fetch data on these cases?
Code is below
import { fetchDoctor } from '../../DoctorActions';
import { getDoctor } from '../../DoctorReducer';
class DoctorDetailPage extends Component {
render() {
return (
<div>{this.props.doctor.name.first}</div>
);
}
}
DoctorDetailPage.need = [params => {
return this.props.dispatch(fetchDoctor(params.id));
}];
function mapStateToProps(state, props) {
return {
doctor: getDoctor(state, props.params.id),
};
}
DoctorDetailPage.propTypes = {
doctor: PropTypes.shape({
insurance: PropTypes.string,
description: PropTypes.string,
GUID: PropTypes.string,
name: PropTypes.shape({
first: PropTypes.string,
last: PropTypes.string,
})
}),
dispatch: PropTypes.func.isRequired,
};
export default connect(mapStateToProps)(DoctorDetailPage);
REDUCER
import { ADD_DOCTOR } from './DoctorActions';
// Initial State
const initialState = { list: [] };
const DoctorReducer = (state = initialState, action = {}) => {
switch (action.type) {
case ADD_DOCTOR:
return {
list: [action.doctor, ...state.list],
};
default:
return state;
}
};
export const getDoctor = (state, id) => {
return state.doctors.list.filter(doctor => doctor._id === id)[0];
};
export default DoctorReducer;
ACTIONS
import callApi from '../../util/apiCaller';
// Export Constants
export const ADD_DOCTOR = 'ADD_DOCTOR';
// Export Actions
export function addDoctor(doctor) {
return {
type: ADD_DOCTOR,
doctor,
};
}
export function addDoctorRequest() {
return () => {
return true;
};
}
export function fetchDoctor(id) {
return (dispatch) => {
return callApi(`doctors/${id}`)
.then(res => dispatch(addDoctor(res)));
};
}
LOG ERROR
TypeError: Cannot read property 'name' of undefined
What is a good approach to fetch data in general?
A user friendly approach would be to enter the page /doctor/123456 wihtout the need of having the doctor available, so user get's instant feedback that his action (navigate me to page x) worked. In onEnter method of react-router or in componentDidMount you should start an action fetchDoctor and in a meanwhile show the user a spinner or a message indicating that the data is being loaded.
render() {
return (
<div>
{ this.props.doctor && <div>{this.props.doctor.name.first}</div> }
{ ! this.props.doctor && <YourSpinnerComponent/> }
</div>
);
}
So the above render method shows something while data is being loaded and when data comes in it displays it without any errors.
What is a good approach to fetch data using redux?
The "good old" way to handle async operations is to use redux-thunk. You can read this great SO answer about dispatching asynchronous redux actions.
The latest trend is to use redux-saga. It is a library that aims to make side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) in React/Redux applications easier and better. More about redux-saga.
So in your case, you would create a Saga to handle the fetching.
More about redux-thunk vs redux-saga in this great SO answer.