The problem is when I update state in Redux, React doesn't run the render function. I am a beginner in Redux so I am not getting what exactly should I be doing to solve this. I read about the #connect function but as I am using CreateReactApp CLI tool, I won't be able to provide support for Decorators without ejecting (Which I dont want to do).
Component:
import React from "react";
import Store from "../store";
Store.subscribe(() => {
console.log(Store.getState().Auth);
});
export default class Login extends React.Component {
login = () => {
Store.dispatch({ type: "AUTH_LOGIN" });
// this.forceUpdate(); If I forceUpdate the view, then it works fine
};
logout = () => {
Store.dispatch({ type: "AUTH_LOGOUT" });
// this.forceUpdate(); If I forceUpdate the view, then it works fine
};
render() {
if (Store.getState().Auth.isLoggedIn) {
return <button onClick={this.logout}>Logout</button>;
} else {
return <button onClick={this.login}>Login</button>;
}
}
}
Reducer:
export default AuthReducer = (
state = {
isLoggedIn: false
},
action
) => {
switch (action.type) {
case "AUTH_LOGIN": {
return { ...state, isLoggedIn: true };
}
case "AUTH_LOGOUT": {
return { ...state, isLoggedIn: false };
}
}
return state;
};
Can anyone please point me in the right direction? Thanks
You can make use of connect HOC instead of decorator, it would be implemented like
import { Provider, connect } from 'react-redux';
import Store from "../store";
class App extends React.Component {
render() {
<Provider store={store}>
{/* Your routes here */}
</Provider>
}
}
import React from "react";
//action creator
const authLogin = () => {
return { type: "AUTH_LOGIN" }
}
const authLogout = () => {
return { type: "AUTH_LOGOUT" }
}
class Login extends React.Component {
login = () => {
this.props.authLogin();
};
logout = () => {
this.props.authLogout();
};
render() {
if (this.props.Auth.isLoggedIn) {
return <button onClick={this.logout}>Logout</button>;
} else {
return <button onClick={this.login}>Login</button>;
}
}
}
const mapStateToProps(state) {
return {
Auth: state.Auth
}
}
export default connect(mapStateToProps, {authLogin, authLogout})(Login);
Related
I made a reducer that fetches admins, and I want it to display certain admins when I call it in my reducer but I am getting Undefined.
I am still very new to redux so apologies for my mistakes.
I tried to include all the relevant folders:
App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../store/actions';
class App extends Component {
async componentDidMount() {
fetch(constants.adminUrl + '/admins/data', {
method: 'GET'
}).then((res) => {
return res.json()
}).then(async (res) => {
this.props.setAdminsInColumns(res.admins)
}).catch((error) => {
toast.error(error.message)
})
}
render() {
return (
{/* SOME CODE */}
);
}
}
let app = connect(null, actions)(App);
export default app;
columnsReducer.js
import { FETCH_ADMINS } from '../actions/types'
import { Link } from 'react-router-dom'
import constants from '../../static/global/index'
import React from 'react';
import { toast } from 'react-toastify'
const initialState = {
admins: [],
{
Header: "Responsible",
accessor: "responsibleAdmin",
style: { textAlign: "center" },
// Place where I want to fetch certain admins and get undefined
Cell: props => <span>{props.value && this.state.admins.name ? this.state.admins.find(admin => admin.id === props.value).name : props.value}</span>
}
}
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_ADMINS:
return { ...state, admins: action.admins}
default:
return state
}
}
index.js
import { FETCH_ADMINS } from "./types"
/**
* starts loader for setting admin
*/
export const setAdminsInColumns = (admins) => async dispatch => {
dispatch({ type: FETCH_ADMINS, admins })
}
types.js
export const FETCH_ADMINS = 'fetch_admins'
When I console.log(action.admins) inside the switch case FETCH_ADMINS in the columnsReducer.js file, I can see all the admin information I want, is there a way to make the state global in the columnsReducer.js file so I can read it?
Any help is appreciated!
use mapStateToProps in the connect method. like below
let mapStateToProps = (state)=>{
return {
admins :[yourcolumnsReducer].admins
}
}
let app = connect(mapStateToProps, actions)(App);
//you can use this.props.admins inside your component
MapStateToProps reference
/keywordsActions
import { UPDATE_KEYWORDS } from "./actionTypes";
import queryString from "query-string";
const keywordsArrayFromUrl = () => {
const query = queryString.parse(window.location.search);
if (query.keywords) {
const removeDuplicate = new Set(query.keywords.split(" "));
return Array.from(removeDuplicate);
}
return [];
};
export function updateKeywords() {
return async dispatch => {
dispatch({
type: UPDATE_KEYWORDS,
payload: await keywordsArrayFromUrl()
});
};
}
/keywordReducer
import { UPDATE_KEYWORDS } from "../actions/actionTypes";
export default function(state = [], action) {
switch (action.type) {
case UPDATE_KEYWORDS:
return action.payload;
default:
return state;
}
}
/SearchBar -- React Component
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
//Redux
import { connect } from "react-redux";
import { updateKeywords } from "../store/actions/KeywordsAction";
class Searchbar extends Component {
constructor(props) {
super(props);
this.state = {
keywords : this.props.keywords
keywordsString: this.props.keywords.join(" ")
};
}
componentDidMount() {
this.props.updateKeywords();
console.log(this.props)
setTimeout(() => console.log(this.props), 10);
}
_handleChange = e => {
this.setState({ keywordsString: e.target.value });
};
_handleSearch = value => {
this.setState({ keywordsString: value });
this.props.history.push(`/search?keywords=${value}`);
};
render() {
return (
<Search
className="Searchbar"
placeholder="Cauta prin iBac..."
value={this.state.keywordsString}
onChange={this._handleChange}
onSearch={this._handleSearch}
/>
);
}
}
const mapStateToProps = state => {
return {
keywords: state.keywords
};
};
export default connect(
mapStateToProps,
{ updateKeywords }
)(withRouter(Searchbar));
I want to save the keywords from the Url to the store and then pass it to the Search bar state.
But i dont understand this :
componentDidMount() {
this.props.updateKeywords();
console.log(this.props); // this.props.keywords is empty
setTimeout(() => console.log(this.props), 10); // After 10 ms this.props.keywords is no empty
}
After 10 ms the props of Searchbar gets updated but the component doesn't render again.
Sorry for my question, I am really new to React / Redux. Please let me know what I am doing wrong. Thank you all!
Update :
componentDidMount() {
this.props.updateKeywords();
setTimeout(() => {
this.setState({
keywordsString: this.props.keywords.join(" ")
});
}, 0);
}
This code is also working... but this other is not working
componentDidMount() {
this.props.updateKeywords();
this.setState({
keywordsString: this.props.keywords.join(" ")
});
}
The reason is that componentDidMount is only called once on mount. What you're looking for is either componentShouldUpdate or componentDidUpdate or the render function, all of which are called when your component receives the updated state from redux. You can read here for more information on what these functions do.
https://reactjs.org/docs/react-component.html#updating
I am trying to add user metadata to my store when mounting a screen. However, when I send the action to the reducer, the store is not modified.
I would expect props after sending the action to be as follows:
{addUserMetaData: ƒ addUserMetaData(user_object),
user: {firestore_doc: {name: "Joe"}}
}
What am i missing here?
To reproduce, react-native-init mwe then add the following code. I've added an image of the app logs below.
App.js
import React, { Component} from 'react';
import { View } from 'react-native';
import Screen from './src/screen';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
const userReducer = function userReducer(state = {}, action) {
console.log('action', action);
switch (action.type) {
case "ADD_USER_METADATA":
return { ...state, firestore_doc: action.payload };
default:
return { ...state };
}
};
const store = createStore(userReducer);
export default class App extends Component {
render() {
return (
<Provider store={store}>
<View>
<Screen />
</View>
</Provider>
);
}
};
src/screen.js
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux';
const addUserMetaData = (user) => ({
type: "ADD_USER_METADATA",
payload: user
})
class Screen extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const user = { name: "Joe" };
console.log('props', this.props);
this.props.dispatch(addUserMetaData(user));
console.log('props after action', this.props);
}
render() {
return (
<View>
<Text>Welcome to react native</Text>
</View>
)
}
}
const mapStateToProps = state => {
return { user: state };
};
export default connect(mapStateToProps)(Screen);
Fixed https://snack.expo.io/#janithar/c3RhY2
Lines I changed
return { ...state, firestore_doc: action.payload };
Please added state.firestore_doc instead of state because in reducer action.payload assign the data in firestore_doc state so you are not getting data from state.user
const mapStateToProps = state => {
return { user: state.firestore_doc };
};
I am in the process of migrating an app from React to React Native and am running into an issue with Redux not dispatching the action to Reducer.
My root component looks like this:
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux';
import Main from '../main/main';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
class App extends Component {
render() {
console.log('Rendering root.js component');
console.log(this.props);
const { dispatch, isAuthenticated, errorMessage, game, communication } = this.props
return (
<View style={styles.appBody}>
<Main
dispatch={dispatch}
game={game}
communication={communication}
/>
</View>
)
}
}
App.propTypes = {
dispatch: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool.isRequired,
errorMessage: PropTypes.string,
}
function mapStateToProps(state) {
const { auth } = state
const { game } = state
const { communication } = state
const { isAuthenticated, errorMessage } = auth
return {
isAuthenticated,
errorMessage,
game,
communication
}
}
const styles = StyleSheet.create({
appBody: {
}
});
export default connect(mapStateToProps)(App)
Then a 'lobby' subcomponent has the dispatch function from Redux as a prop passed to it. This component connects to a seperate javascript file, and passes the props to it so that that seperate file has access to the dispatch function:
componentWillMount() {
coreClient.init(this);
}
In that file I do this:
const init = function(view) {
socket.on('connectToLobby', (data) => {
console.log('Lobby connected!');
console.log(data);
console.log(view.props) // shows the dispatch function just fine.
view.props.dispatch(connectLobbyAction(data));
});
}
The action itself also shows a console log I put there, just that it never dispatches.
export const LOBBY_CONNECT_SUCCESS = 'LOBBY_CONNECT_SUCCESS';
export function connectLobbyAction(data) {
console.log('Action on connected to lobby!')
return {
type: LOBBY_CONNECT_SUCCESS,
payload: data
}
}
I feel a bit lost, would appreciate some feedback :)
EDIT: Reducer snippet:
var Symbol = require('es6-symbol');
import {
LOBBY_CONNECT_SUCCESS
} from './../actions/actions'
function game(state = {
//the state, cut to keep things clear.
}, action) {
switch (action.type) {
case LOBBY_CONNECT_SUCCESS:
console.log('reducer connect lobby')
return Object.assign({}, state, {
...state,
user : {
...state.user,
id : action.payload.id,
connected : action.payload.connected
},
match : {
...state.match,
queuePosition : action.payload.position,
players : action.payload.playerList,
room : 'lobby'
},
isFetching: false,
})
default:
return state
}
}
const app = combineReducers({
game,
//etc.
})
I work on app with facebook login using react-native and redux. Right now I'm face to an issue :
Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
So I think I have to use componentWillMount() just before my render method, but I don't know how to use it ..
containers/Login/index.js
import React, { Component } from 'react';
import { View, Text, ActivityIndicatorIOS } from 'react-native';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actionCreators from '../../actions';
import LoginButton from '../../components/Login';
import reducers from '../../reducers';
import { Card, CardSection, Button } from '../../components/common';
class Login extends Component {
// how sould I use it ?
componentWillMount() {
}
render() {
console.log(this.props.auth);
const { actions, auth } = this.props;
var loginComponent = <LoginButton onLoginPressed={() => actions.login()} />;
if(auth.error) {
console.log("erreur");
loginComponent = <View><LoginButton onLoginPressed={() => actions.login()} /><Text>{auth.error}</Text></View>;
}
if (auth.loading) {
console.log("loading");
loginComponent = <Text> LOL </Text>;
}
return(
<View>
<Card>
<CardSection>
{ auth.loggedIn ? this.props.navigation.navigate('Home') : loginComponent }
</CardSection>
</Card>
</View>
);
}
}
function mapStateToProps(state) {
return {
auth: state.auth
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actionCreators, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
the reducer :
import { LOADING, ERROR, LOGIN, LOGOUT } from '../actions/types';
function loginReducer(state = {loading: false, loggedIn: false, error: null}, action) {
console.log(action);
switch(action.type) {
case LOADING:
console.log('Inside the LOADING case');
return Object.assign({}, state, {
loading: true
});
case LOGIN:
return Object.assign({}, state, {
loading: false,
loggedIn: true,
error: null,
});
case LOGOUT:
return Object.assign({}, state, {
loading: false,
loggedIn: false,
error: null
});
case ERROR:
return Object.assign({}, state, {
loading: false,
loggedIn: false,
error: action.err
});
default:
return state;
}
}
export default loginReducer;
and the action :
import {
LOADING,
ERROR,
LOGIN,
LOGOUT,
ADD_USER
} from './types';
import { facebookLogin, facebookLogout } from '../src/facebook';
export function attempt() {
return {
type: LOADING
};
}
export function errors(err) {
return {
type: ERROR,
err
};
}
export function loggedin() {
return {
type: LOGIN
};
}
export function loggedout() {
return {
type: LOGOUT
};
}
export function addUser(id, name, profileURL, profileWidth, profileHeight) {
return {
type: ADD_USER,
id,
name,
profileURL,
profileWidth,
profileHeight
};
}
export function login() {
return dispatch => {
console.log('Before attempt');
dispatch(attempt());
facebookLogin().then((result) => {
console.log('Facebook login success');
dispatch(loggedin());
dispatch(addUser(result.id, result.name, result.picture.data.url, result.picture.data.width, result.data.height));
}).catch((err) => {
dispatch(errors(err));
});
};
}
export function logout() {
return dispatch => {
dispatch(attempt());
facebookLogout().then(() => {
dispatch(loggedout());
})
}
}
If you need more code here is my repo :
https://github.com/antoninvroom/test_redux
componentWillMount is one the first function to be run when creating a component. getDefaultProps is run first, then getInitialState then componentWillMount. Both getDefaultProps and getInitialState will be run only if you create the component with the react.createClass method. If the component is a class extending React.Component, those methods won't be run. It is recommended to use componentDidMount if you can instead of componentWillMount because your component can still be updated before componentWillMount and the first render.
You can find more info on the react component lifecycle here
Also, it is recommended to set the state or the default props inside the class constructor or using getDefaultProps and getInitialState.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { bar: 'foo' };
}
static defaultProps = {
foo: 'bar'
};
}
EDIT: Here's the component handling login
import React, { Component } from 'react';
import { View, Text, ActivityIndicatorIOS } from 'react-native';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actionCreators from '../../actions';
import LoginButton from '../../components/Login';
import reducers from '../../reducers';
import { Card, CardSection, Button } from '../../components/common';
class Login extends Component {
componentDidMount() {
// If user is already logged in
if(this.props.auth.loggedIn) {
// redirect user here
}
}
componentWillReceiveProps(nextProps) {
// If the user just log in
if(!this.props.auth.loggedIn && nextProps.auth.loggedIn) {
// Redirect user here
}
}
render() {
console.log(this.props.auth);
const { actions, auth } = this.props;
var loginComponent = <LoginButton onLoginPressed={() => actions.login()} />;
if(auth.error) {
console.log("erreur");
loginComponent = <View><LoginButton onLoginPressed={() => actions.login()} /><Text>{auth.error}</Text></View>;
}
if (auth.loading) {
console.log("loading");
loginComponent = <Text> LOL </Text>;
}
return(
<View>
<Card>
<CardSection>
{ auth.loggedIn ? this.props.navigation.navigate('Home') : loginComponent }
</CardSection>
</Card>
</View>
);
}
}
function mapStateToProps(state) {
return {
auth: state.auth
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actionCreators, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Based on your comment to Ajay's answer, you are looking to set the initial state in the component. To do so, you would set the state inside the constructor function.
class Login extends Component {
constructor(props) {
super(props);
this.state = {
color: props.initialColor
};
}
If you have data that is fetched asynchronously that is to be placed in the component state, you can use componentWillReceiveProps.
componentWillReceiveProps(nextProps) {
if (this.props.auth !== nextProps.auth) {
// Do something if the new auth object does not match the old auth object
this.setState({foo: nextProps.auth.bar});
}
}
componentWillMount() is invoked immediately before mounting occurs. It is called before render(), therefore setting state in this method will not trigger a re-rendering. Avoid introducing any side-effects or subscriptions in this method.
if you need more info componentWillMount()
read this https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/birth/premounting_with_componentwillmount.html