I created a simple React component for generating menu, and I wanted to replace it's visibility toggling with Redux (instead of state).
My component looks like this:
class SiteMenu extends React.Component {
constructor(props) {
super(props);
}
toggle(force = false) {
let active = !active;
store.dispatch({
type: 'TOGGLE',
active
});
}
render() {
const wrapperClass = this.props.active ? `${this.props.className}__wrapper ${this.props.className}__wrapper--active` : `${this.props.className}__wrapper`;
return (
<nav className={this.props.className} ref="nav">
<button className={`${this.props.className}__trigger`} onClick={this.toggle.bind(this)}>
{this.props.active}
</button>
<ul className={wrapperClass}>
</ul>
</nav>
);
}
}
I added mapStateToProps:
const mapStateToProps = (store) => {
return {
active: store.menuState.active
}
};
and connect
connect(mapStateToProps)(SiteMenu);
My store:
import { createStore } from 'redux';
import reducers from './reducers/index.js';
const store = createStore(reducers, window.devToolsExtension && window.devToolsExtension());
export default store;
and reducers:
import { combineReducers } from 'redux';
import menuReducer from './menu-reducer';
const reducers = combineReducers({
menuState: menuReducer
});
export default reducers;
const initialMenuState = {
active: false
};
const menuReducer = (state = initialMenuState, action) => {
switch(action.type) {
case 'TOGGLE':
console.log(action);
return Object.assign({}, state, { active: action.active });
}
return state;
};
export default menuReducer;
When I check my Redux DevTools, state is changing. What should I do?
Code in the repo: https://github.com/tomekbuszewski/react-redux
to use connect func , also you should add Provider from react-redux
render(<Provider store={store}><Parent /></Provider>, app);
then you should add wrapped component to Parent component
const SiteMenuWrapped = connect(mapStateToProps)(SiteMenu);
///in Parent component
<Header className="site-header">
<SiteMenuWrapped
className="site-navigation"
content={this.state.sections}
/>
</Header>
Few issues:
connect returns a higher order component, so best advice is to split out each component to a separate file and export the result of connect. See the examples at e.g. http://redux.js.org/docs/basics/ExampleTodoList.html. This means that your mapStateToProps function is never being called, hence why this.props.active is always undefined.
Your store registration is incorrect, the second parameter to createStore is the initial state. To register the Chrome Redux dev tools see https://github.com/zalmoxisus/redux-devtools-extension
You should use <Provider> to make the store available to all components. See the Redux docs.
You can dispatch actions through this.props.dispatch or use mapDispatchToProps, see https://github.com/reactjs/react-redux/blob/master/docs/api.md
Related
I need to change the "global" state of Redux (I believe it's called storage). This is my code:
reducer
export const user = (state = {}, action) => {
console.log(4);
console.log(action.type)
console.log(action.payload)
switch (action.type) {
case C.SET_USER:
console.log(action.payload);
return action.payload;
case C.CLEAR_USER:
return action.payload;
default:
return state;
}
};
Action:
export const setUser = (user = {}) => {
console.log(user);
return {
type: C.SET_USER,
payload: user,
}
};
Calling the action:
const user = {test:true};
setUser(this.state.user);
But if I run this code, it fails and doesn't call the reducer. It calls the action, but not the reducer. What am I missing?
My current app.js code:
export default class App extends Component {
constructor(p) {
super(p);
this.state = {user: null};
}
setUser = () => {
const {uid} = firebase.auth().currentUser;
firebase.database().ref('Users').child(uid).on('value', r => {
const user = r.val();
this.setState({user: user});
console.log(this.state.user);
setUser(this.state.user);
});
};
componentWillMount() {
if (firebase.auth().currentUser) {
this.setUser();
}
firebase.auth().onAuthStateChanged(async () => {
console.log('authChanged');
if (!firebase.auth().currentUser) {
return null;
}
this.setUser();
});
}
render() {
return (
<div className="App">
<Nav/>
</div>
);
}
}
setUser have to be dispatched and not simply called:
store.dispatch(setUser(user));
But that's not really the react way, you'd better use mapDispatchToProps in your connect function to dispatch actions directly from component props. Something along the lines of:
import { setUser } from 'store/user';
// ...
class UserComponent extends React.Component {
// ...
someMethod() {
this.props.setUser(user);
}
}
export default connect(
null,
({setUser: setUser})
)(UserComponent);
This allows your React component to be linked to your Redux store in an optimized and bug-free way. That's also the way most developer use, so you're likely to find a lot of docs on this.
Example: Your connected Component where you want to use your setUser action with redux
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setUser} from '../../actions';
class YourComponent extends Component {
render(){
// now your redux action is passed to component as prop
// and you can use it like
this.props.setUser(some-user);
return()
}
}
export default connect(null, {setUser})(YourComponent);
first of all you have to dispatch action to change the state , second you have to connect your component to the store
to connect your component to the store
...
import { connect } from 'react-redux';
class MyComponent extends React.Component {
...
}
export default connect((store) => ({...}))
when you connect your component to the store you will have access to dispatch function in the props
to dispatch action do this :
this.props.dispatch(setUser());
I believe it's called storage
BTW it called store
I am new Redux developer. I am trying to create a tab component using React and Redux in a web application.
When I select a tab I can in the console and in Redux Dev Tool that the state is changed, but as soon it changes it turns back to initial state (it happens to '' or 'tabData' or 'tabBulletin' in tabReducer.js
I don't understand it to solve this logical issue. Please, could anyone help me get wiser on this?
Thank you.
This is the parent React Tags related to the issue
<TabsHeader>
<TabHeader id='tab1-tab' label='Data | 34' target='tabData' />
<TabHeader id='tab2-tab' label='Bulletins | 35' target='tabBulletin' />
</TabsHeader>
<TabsContent>
</TabsContent>
TabHeader.js
import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { selectTab } from './tabActions'
class TabHeader extends Component {
render() {
const selected = this.props.tab.selected === this.props.target
return (
<a href='/' id={this.props.id}
onClick={() => this.props.selectTab(this.props.target)}
data-target={this.props.target}
className={selected ? 'active' : '' }
>
{this.props.label}
</a>
)
}
}
const mapStateToProps = state => ({ tab: state.tab })
const mapDispatchToProps = dispatch => bindActionCreators({ selectTab },
dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(TabHeader)
tabActions.js
export function selectTab(tabId) {
console.log(tabId)
return {
type: 'TAB_SELECTED',
payload: tabId
}
}
tabReducer.js
const INITIAL_STATE = { selected: 'tabData' }
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'TAB_SELECTED':
return { ...state, selected: action.payload }
default:
return state
}
}
You dispatched wrong way. Where did you read this ?
dispatch => bindActionCreators({ selectTab }, dispatch)
All you need is just a simple dispatch like this:
dispatch => ({ selectTab: tabId => selectTab(tabId) })
That's is.
I am back again and now with the solution:
The problem is here on the tag of the component in TabHeader.js:
This makes the site to be reloaded turn it to initial state
So I changed to "javaxritp:;" which solves the issue with Redux.
The line below is to map action creators that allows to be trigged to send to reducers to develop state. I may loosing a small thing that makes Redux turn it back to initial state. I learnt that from my React class. Thank you anyway.
const mapDispatchToProps = dispatch => bindActionCreators({ selectTab }, dispatch)
dispatch => bindActionCreators({ selectTab }, dispatch)
I'm trying implement Redux to my app. So, I created action, reducer, store etc... and now I have to pass state to reducer and change value of this parameter (boolean). I don't know what I'm doing wrong. An alert in reducer is triggered after click button, but dialog doesn't close. Any idea how to change value of open?
Action
export const checkPassword = () => ({
type: "CHECK_PASSWORD"
});
Component
const mapDispatchToProps = dispatch => {
return {
checkPassword: () => dispatch({type: 'CHECK_PASSWORD'})
};}
function mapStateToProps(state, open) {
return {
open: state.open,
};}
class StartDialog extends Component {
constructor(props) {
super(props);
this.state = { open: true };
}
render() {
const actions = [ <FlatButton label="Submit" onClick={this.props.checkPassword} /> ];
return (
<Dialog title="Welcome to the React App!" actions={actions} open={this.state.open} ></Dialog>
);}}
const StartForm = connect(mapStateToProps, mapDispatchToProps)(StartDialog);
export default StartForm;
Reducer
import { CHECK_PASSWORD } from "../constants/action-types";
const initialState = {
open: true
};
const checkpasswordReducer = (state = initialState, action) => {
switch (action.type) {
case CHECK_PASSWORD:
alert('action!')
return {...state, open: false};
default:
return state;
}};
export default checkpasswordReducer;
Store
import { createStore } from "redux";
import rootReducer from "../reducers/index";
const store = createStore(
rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
Reducer index.js
import { combineReducers } from "redux";
import checkpasswordReducer from "./checkpasswordReducer";
export default combineReducers({ checkPassword: checkpasswordReducer
});
open will be in props not in state
change your render to this
<Dialog title="Welcome to the React App!" actions={actions} open={this.props.open} ></Dialog>
also in mapStateToProps function open value will be in state object so you don't need the second parameter in the function
function mapStateToProps(state) {
return {
open: state.checkPassword.open,
};
}
When you are using redux and reading the value from store you need to use it from props in your component. In short you should not have a state that is directly derivable from props. Change your component to below and it should work
class StartDialog extends Component {
render() {
const actions = [ <FlatButton label="Submit" onClick={this.props.checkPassword} /> ];
return (
<Dialog title="Welcome to the React App!" actions={actions} open={this.props.open} ></Dialog>
);
}
}
Also in your mapStateToProps you need to access the state correctly, If you are using combineReducers, you need to access the open value from the corresponding reducer
So if you use combineReducer like
const reducers = combineReducer({
checkPassword:checkpasswordReducer
})
you need to use your mapStateToProps function like
function mapStateToProps(state) {
return {
open: state.checkPassword.open,
};
}
I am trying to get a simple example to work. Here is the code below.
In this example, in:
mapStateToProps = (state) => {}
where is state coming from? I am little confused as to what exactly I am passing into?
I understand that connect(mapStateToProps)(TodoApp) "binds" the state returned in mapStateToProps to TodoApp and can then be accessed via this.props.
What do I need to do to this code so I can print out the current state inside TodoApp
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import { connect } from 'react-redux'
import { createStore } from 'redux'
import { combineReducers } from 'redux'
const stateObject = [
{
'id': 1,
'name': 'eric'
},
{
'id': 2,
'name': 'john'
}
]
const todo = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text
}
default:
return state
}
}
const todos = (state = stateObject, action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
];
default:
return state
}
}
const store = createStore(todos)
//confused by what is happening here
const mapStateToProps = (state) => {
return {
?: ?
}
}
const TodoApp = () => {
//How do I get this to print out the current props?
console.log(this.props)
return (
<div>
Some Text
</div>
)
}
connect(mapStateToProps)(TodoApp)
ReactDOM.render(
<Provider store={store} >
<TodoApp />
</Provider>,
document.getElementById('root')
)
Ok updated:
const mapStateToProps = (state) => {
return {
names: state
}
}
const TodoApp = () => {
console.log(this.props)
return (
<div>
Some Text1
</div>
)
}
const ConnectedComponent = connect(mapStateToProps)(TodoApp);
ReactDOM.render(
<Provider store={store} >
<ConnectedComponent />
</Provider>,
document.getElementById('root')
)
However I'm still getting undefined for console.log(this.props).
What am I doing wrong?
There's no this with a functional component. To access the props you can change it to this:
const TodoApp = (props) => {
console.log(props)
return (
<div>
Some Text1
</div>
)
}
mapStateToProps maps the some parts of your Redux state to props of your React Component.
State comes from your store. In fact, you can take a look at your current state at any point by calling store.getState(). When you do createStore(todos), this creates the state based on the todos reducer. As you can see in your todos reducer, your initial state comes from stateObject, which is defined up top.
So, back to mapStateToProps. All you need to do in that functions is to return the object, where keys will be the props and values will be the values obtained from the Redux state. Here's an example of mapStateToProps:
const mapStateToProps = function (state) {
return {
propName: state
}
}
Now when you do the console.log(this.props) inside render(), you can see the whole state being stored inside this.props.propName. That is achieved by mapStateToProps.
A little bit of theory on this: each time an action is dispatched, every mapStateToProps you have in your app is called, props are applied to every component you created, and if any props have changed, that component will re-render. This kind of behaviour is provided for you via connect function. So you don't have to implement this behaviour for every component: all you need to do is to apply it like so: const ConnectedComponent = connect(mapStateToProps)(SomeComponent) and use ConnectedComponent instead of SomeComponent.
I am currently following this tutorial. I've hit a bit of a snag involving mapStateToProps in the following code:
import React from 'react';
import Voting from './voting';
import {connect} from 'react-redux';
const mapStateToProps = (state) => {
return {
pair: state.getIn(['vote','pair']),
winner: state.get('winner')
};
}
const VotingContainer = connect(mapStateToProps)(Voting);
export default VotingContainer;
Here is the Voting component that's imported:
import React from 'react';
import Vote from './Vote';
import Winner from './winner';
const Voting = ({pair,vote,hasVoted,winner}) =>
<div>
{winner ? <Winner winner={winner}/> :
<Vote pair={pair} vote={vote} hasVoted={hasVoted}/>
}
</div>
export default Voting;
It is supposed to render two buttons from the pair prop. The vote prop is a function that will be executed on click, hasVoted disables buttons when true and winner only renders the winner component as shown.
The state is expected to be an immutableJS map that looks like this:
Map({
vote:{
pair:List.of('Movie A','Movie B')
}
});
Instead I am getting an error saying that state is undefined in the state.getIn line.
The code setting the state is in index:
const store = createStore(reducer);
const socket = io(document.location.protocol + '//' + document.location.hostname + ':8090');
socket.on('state', state => store.dispatch({
type: 'SET_STATE',
state
}));
I have logged store.getState()after setting and it is as expected but undefined in mapStateToProps. I also logged the state variable in above context and it's also as expected.
I also set the state normally and it surprisingly works!:
store.dispatch({
type: 'SET_STATE',
state: {
vote: {
pair: ['Movie A', 'Movie B']
}
}
});
The value of state above is exactly what is received from the server
Lastly here's what my reducer looks like:
import React from 'react';
import {Map, fromJS} from 'immutable';
const reducer = (state = Map(), action) => {
switch (action.type) {
case 'SET_STATE':
return state.merge(action.state);
}
}
export default reducer;
What am I doing wrong?
EDIT: I realised that mapStateToProps is not being called after the store.dispatch(). I went through the docs for the possible reasons mapStateToProps is not being called and it's not one of them.
You reducer doesn't have a default action in switch statement. Which is why even though you mentioned the initial state in reducer params, undefined is returned as store initial state
import React from 'react';
import {Map,fromJS} from 'immutable';
const reducer = (state = Map() ,action) => {
switch(action.type){
case 'SET_STATE': return state.merge(action.state);
default:
return state;
}
}
export default reducer;
Adding the default statement will fix the issue :)
I ended up here too because I had failed to pass my rootreducer function to my createStore method. I had:
const store = createStore(applyMiddleware(...middlewares));
I needed:
const store = createStore(rootReducer(), applyMiddleware(...middlewares));