I'm new to the site, and new to React. I would be very happy if someone would help me please.
I built a chat, which allows messages to be sent between users, and everything works great. But there is a problem with the mapStateToProps in my opinion, because when I add a new message, the state itself only changes when I refresh the page. I want it to change immediately, that I will see the conversation in chat immediately and not in the refresh of the page.
This is the code I wrote down, please I would be happy if someone would help me, if I need to add more code I will do it.
Explanation of the code, I have a chat component, where I do mapStateToProps, and use state what redux, I have another chatReducer file, which is responsible for managing the state of the chat, and in fact maybe there is a problem, because I update the state, I do not get it. Only after refreshing the page, I get it.
I have another chatAction file - through which I call chatReducer.
I think the problem is very small, I probably did not enter the correct code in ChatReducer or mapStateToProps, but other than that everything works.
Please I would be happy if anyone would help me, I am new here on the site.
Another thing, the problem is this, I have an array of conversations. It comes from the mapStateToProps, from the state.chat. Once I add a new call through the submitMessage function, I manage to add the message to the database, but the state itself is not updated directly, that's the problem. I want when someone sends a straight message that it will be updated in chat.
chat component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getRealtimeUsers } from '../redux/actions/chatActions';
import { updateMessage } from '../redux/actions/chatActions';
import { getRealtimeConversations } from '../redux/actions/chatActions';
class chat extends Component {
state = {
message: '',
chatStarted: false,
chatUser: '',
userUid: null
}
componentDidMount() {
this.props.getRealtimeUsers();
}
initChat = (user) => {
this.setState({
chatStarted: true,
chatUser: user.handle,
userUid: user.handle
});
}
submitMessage = (e) => {
const msgObj = {
user_uid_1: this.props.user.credentials.handle,
user_uid_2: this.state.userUid,
message: this.state.message
}
if (this.state.message !== "") {
this.props.updateMessage(msgObj);
this.setState({ message: '' });
}
console.log(msgObj);
}
render() {
return (
<section>
<div>
<div>
{
this.state.chatStarted ?
this.props.chat.conversations.map(con =>
<div>
<p>{con.message}</p>
</div>)
: null
}
</div>
{
this.state.chatStarted ?
<div>
<textarea
value={this.state.message}
onChange={(e) => this.setState({ message: e.target.value })}
placeholder="Write Message"
/>
<button onClick={this.submitMessage}>Send</button>
</div> : null
}
</div>
</section>
);
}
}
const mapStateToProps = (state) => ({
data: state.data,
chat: state.chat,
user: state.user
});
export default connect(
mapStateToProps,
{ getRealtimeUsers, updateMessage, getRealtimeConversations }
)(chat);
chat reducer
import { userConstants } from "../types"
const initialState = {
users: [],
conversations: []
}
export default function (state = initialState, action) {
switch (action.type) {
case `${userConstants.GET_REALTIME_USERS}_REQUEST`:
return {
...state
}
case `${userConstants.GET_REALTIME_USERS}_SUCCESS`:
return {
...state,
users: action.payload
}
case userConstants.GET_REALTIME_MESSAGES:
return {
...state,
conversations: action.payload
}
case `${userConstants.GET_REALTIME_MESSAGES}_FAILURE`:
return {
...state,
conversations: []
}
default:
return state;
}
}
chat actions
import { userConstants } from "../types";
import axios from 'axios';
export const getRealtimeUsers = () => (dispatch) => {
dispatch({ type: `${userConstants.GET_REALTIME_USERS}_REQUEST` });
axios
.get('/realtimeUsers')
.then((res) => {
console.log(res);
dispatch({
type: `${userConstants.GET_REALTIME_USERS}_SUCCESS`,
payload: res.data
});
})
.catch((err) => console.log(err))
}
export const updateMessage = (msgObj) => (dispatch) => {
axios.post('/updateMessage', msgObj)
.then(() => {console.log("message added") })
.catch((err) => console.log(err));
}
export const getRealtimeConversations = (user) => (dispatch) => {
axios.get('/realtimeConversations',
{
params: {
user: JSON.stringify(user)
}
}
)
.then((res) => {
dispatch({
type: userConstants.GET_REALTIME_MESSAGES,
payload: res.data
});
})
.catch((err) => console.log(err))
}
Related
I have followed a tutorial that centralizes React app error management with Redux in an errorReducer file:
errorReducer.js
import { GET_ERRORS } from "../actions/types";
const initialState = {};
export default function (state = initialState, action) {
switch (action.type) {
case GET_ERRORS:
return action.payload;
default:
return state;
}
}
That way, whenever there's an error in any http request, the content of the error is stored only in one place in the app:
authActions.js
export const registeruser = (userData, history) => (dispatch) => {
axios
.post("/api/users/register", userData)
.then((res) => {
history.push("/login-page");
})
.catch((err) => {
dispatch({
type: GET_ERRORS,
payload: err.response.data,
});
});
};
And this is how the error response is managed in Register component:
RegisterPage.js
export class RegisterPage extends Component {
constructor() {
super();
this.state = {
errors: {},
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({ errors: nextProps.errors });
}
}
render() {
const { errors } = this.state;
return (
<>
<div>
{Object.values(errors).map((value) => (
<p>{value}</p>
))}
</div>
</>
);
}
}
RegisterPage.propTypes = {
errors: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
errors: state.errors,
});
export default connect(mapStateToProps, { registeruser })(
withRouter(RegisterPage)
);
#NOTE: I only displayed the code that is relevant to error management.
The same logic is applied for all components.
Sometimes, I would like to display a text when a request is succesful and not necessarily perform an action like the one above:
history.push("/login-page");
What I would like to know is:
Does it make sense to create a success reducer, similar to the error reducer, that I can use to centralize the logic to be executed when any http request has been successful inside React components and display a message accordingly.
In the last couple of days I have been working on my Redux api call. I am actually having a problem getting the data back to the view component. Currently I'm able to see the data in the in the action generator, so I know at least I'm able to get it. However, nothing is showing in the view. I imagine it may have something to do with when it's loading. This is why I tried to load it when the component is rendering.
https://djangoandreact.herokuapp.com/user/1 is what is not loading.
codesandbox: https://codesandbox.io/s/zlor60q3jm?from-embed
Should be able to go to /user/1 at the end similar to going to /1 brings up an article(Tough Hope)
Heres the view component:
import React from "react";
import { connect } from "react-redux";
import { fetchUser } from "../store/actions/userActions";
class UserDetailView extends React.Component {
componentDidMount() {
const userID = this.props.match.params.userID;
fetchUser(userID); //fixed
}
render() {
const { user } = this.props.user;
console.log(user);
return (
<div>
<h3>{user.username}</h3>
</div>
);
}
}
const mapStateToProps = state => ({
user: state.user
});
const mapDispatchToProps = (dispatch, ownProps) => ({
fetchUser: dispatch(fetchUser(ownProps.match.params.userID))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserDetailView);
Action generator
import axios from "axios";
import { thunk } from "react-redux";
export function fetchUser(userID) {
console.log(userID);
return dispatch => {
return axios.get(`/api/user/${userID}`).then(res => {
dispatch(fetchUserSuccess(res.data));
console.log(res.data); // loads data
});
};
}
// Handle HTTP errors since fetch won't.
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
export const FETCH_USER_BEGIN = "FETCH_USER_BEGIN";
export const FETCH_USER_SUCCESS = "FETCH_USER_SUCCESS";
export const FETCH_USER_FAILURE = "FETCH_USER_FAILURE";
export const fetchUserBegin = () => ({
type: FETCH_USER_BEGIN
});
export const fetchUserSuccess = user => ({
type: FETCH_USER_SUCCESS,
payload: { user }
});
export const fetchUserFailure = error => ({
type: FETCH_USER_FAILURE,
payload: { error }
});
Reducers(which are probably fine):
import {
FETCH_USER_BEGIN,
FETCH_USER_SUCCESS,
FETCH_USER_FAILURE
} from "../actions/actionTypes";
const initialState = {
user: {},
loading: false,
error: null
};
export default function userReducer(state = initialState, action) {
switch (action.type) {
case FETCH_USER_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_USER_SUCCESS:
return {
...state,
loading: false,
user: action.payload.user
};
case FETCH_USER_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
user: {}
};
default:
return state;
}
}
folks. I found it.
case FETCH_USER_SUCCESS:
return {
...state,
loading: false,
user: action.payload.user
};
user is supposed to be user:action.payload
Also, the user action was supposed to be
export const fetchUserSuccess = user => ({
type: FETCH_USER_SUCCESS,
payload: user
})
WOOOOW. But, honestly, I learned so much about Redux in the last two sleepless nights, it was worth the pain. Really was. Now, instead of copy pasta, I know what an action generator is and does, and reducer (obvi)
I'm trying to log user in with firebase authentication and I ran into this problem that when I use onAuthStateChanged method in action creator to get is user logged in before or not. When users is logged in before so I just call login success and navigate them to main screen, if not I just dispatch another action and show login screen, but I still see login screen for like half a second if login success action dispatched, I'm new to react native and redux, can anyone help me solve this issue, thank you guys so much!!
here is my index.js
import { checkLogin } from './actions';
class LoginScreen extends Component {
componentDidMount() {
this.props.checkLogin();
}
componentWillReceiveProps(nextProp) {
if (nextProp.loggedIn) {
this.props.navigation.navigate('main');
}
}
//... some code
render() {
if (this.props.isChecking) {
return <Spinner />
}
return this.renderContent();
}
}
const mapStateToProps = ({ auth }) => {
const { email, password, error, loading, loggedIn, isChecking } = auth;
return { email, password, error, loading, loggedIn, isChecking };
};
export default connect(mapStateToProps, {
emailChanged, passwordChanged, emailLogin, checkLogin
})(LoginScreen);
and here is my actions.js
import firebase from 'firebase';
export const checkLogin = () => {
return(dispatch) => {
firebase.auth().onAuthStateChanged(user => {
if (user) {
dispatch({ type: EMAIL_LOGIN_SUCCESS, payload: user });
} else {
dispatch({ type: CHECK_LOGIN_FAILED });
}
})
}
}
// some code
and reducer.js
const INITIAL_STATE = {
// ...
isChecking: true,
}
export default (state = INITIAL_STATE, action) => {
console.log(action);
switch (action.type) {
// ...
case CHECK_LOGIN_FAILED:
return { ...state, isChecking: false };
case EMAIL_LOGIN_SUCCESS:
return { ...state, user: action.payload, error: '', loading: false, email: '', password: '', loggedIn: true, isChecking: false };
default:
return state;
}
}
I think this happens because the previous state of isChecking is remembered by your reducer from the previous rendering of your component. I bet if you reset the content and settings of your simulator that you would not first see the login page. However, to fix this I would recommend dispatching an action before firebase.auth().onAuthStateChanged to tell your reducer that you are in an "isChecking" state.
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 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.