Redux. Pass state value to reducer - javascript

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,
};
}

Related

redux state gets -improperly- updated before reducers is called (w/ ReactDnD)

Edit: the bug was is a separated helper function that was mutating the state (not displayed in the post).
I'm experimenting with ReactDnD to create a sortable image grid via drag and drop. I've been following this tutorial 1 and trying to implement it with redux instead of React Context.
The issue that I'm having is that my props don't get updated after I re-arrange the images. I have been debugging the reducers and noticed that the state gets somehow updated before the reducer has the chance to do so (which would trigger mapStateToProps to reload my component with the updated state). The problem though it that I have no idea why that happens. I have the feeling that since ReactDnD is also using Redux, it's somehow causing this.
Here are the different parts:
Index.js
export const store = createStore(reducers, applyMiddleware(thunk))
ReactDOM.render(
<Provider store={store}>
<DndProvider backend={HTML5Backend}>
<App />
</DndProvider>
</Provider>,
document.getElementById('root')
)
App.js (parent component of DroppableCell and DraggableItem)
class App extends React.Component {
componentDidMount() {
this.props.loadCollection(imageArray)
}
render() {
return (
<div className='App'>
<div className='grid'>
{this.props.items.map((item) => (
<DroppableCell
key={item.id}
id={item.id}
onMouseDrop={this.props.moveItem}
>
<DraggableItem src={item.src} alt={item.name} id={item.id} />
</DroppableCell>
))}
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return { items: state.items }
}
export default connect(mapStateToProps, {
moveItem,
loadCollection,
})(App)
DroppableCell (calling the action creator from parent component)
import React from 'react'
import { useDrop } from 'react-dnd'
const DroppableCell = (props) => {
const [, drop] = useDrop({
accept: 'IMG',
drop: (hoveredOverItem) => {
console.log(hoveredOverItem)
props.onMouseDrop(hoveredOverItem.id, props.id)
},
})
return <div ref={drop}>{props.children}</div>
}
export default DroppableCell
DraggableItem
import React from 'react'
import { useDrag } from 'react-dnd'
const DraggableItem = (props) => {
const [, drag] = useDrag({
item: { id: props.id, type: 'IMG' },
})
return (
<div className='image-container' ref={drag}>
<img src={props.src} alt={props.name} />
</div>
)
}
export default DraggableItem
Reducer
import { combineReducers } from 'redux'
const collectionReducer = (state = [], action) => {
// state is already updated before the reducer has been run
console.log('state:', state, 'action: ', action)
switch (action.type) {
case 'LOAD_ITEMS':
return action.payload
case 'MOVE_ITEM':
return action.payload
default:
return state
}
}
export default combineReducers({
items: collectionReducer,
})
The action creator
export const moveItem = (sourceId, destinationId) => (dispatch, getState) => {
const itemArray = getState().items
const sourceIndex = itemArray.findIndex((item) => item.id === sourceId)
const destinationIndex = itemArray.findIndex(
(item) => item.id === destinationId
)
const offset = destinationIndex - sourceIndex
//rearrange the array
const newItems = moveElement(itemArray, sourceIndex, offset)
dispatch({ type: 'MOVE_ITEM', payload: newItems })
}
found the bug - unfortunately was outside the code posted as I thought it was a simple helper function. I realised I was using the 'splice' method to rearrange the imageArray, and therefore mutating the state.

react and redux, update state from store changes in a component

I'm trying to update my home componentstate by getting data from the redux store every time the store is updated. I'm not sure what's wrong with the code below. I can't listen to store changes in my `home component.
my dispatch function is handled in this class.
export class GanttFilter extends Component {
...
handleSubmit = () => {
this.gFilter.filterGanttData(this.formValues)
.then((result) => {
if (result.data)
this.props.dispatch(ganttActions.loadGanttData(result.data));
});
}
...
GanttFilter.propTypes = {
dispatch: PropTypes.func.IsRequired
};
function mapStateToProps(state) {
return {
ganttData: state.gantt.ganttData
};
}
export default connect(mapStateToProps)(GanttFilter);
What I would like to do every time dispatch is called and the data changes, is update the state in my home component. Here is the component.
export class Home extends Component {
constructor() {
super();
this.state = {
data: [],
links: []
};
}
render() {
return (
<div className="fill">
<Gantt data={this.state.data} links={this.state.links} />
</div>
);
}
}
Home.propTypes = {
data: PropTypes.IsRequired
};
function mapStateToProps(state) {
return {
data: state.gantt.ganttData
};
}
export default connect(mapStateToProps)(Home);
the function mapStateToProps is never hit when I set a break point. How can I listen to changes to the store from the home component and update state?
Edit: Here is the wrapper component
function renderApp() {
// This code starts up the React app when it runs in a browser. It sets up the routing
// configuration and injects the app into a DOM element.
const baseUrl = document.getElementsByTagName("base")[0].getAttribute("href");
ReactDOM.render(
<ReduxProvider store={store}>
<AppContainer>
<BrowserRouter children={routes} basename={baseUrl} />
</AppContainer>
</ReduxProvider>,
document.getElementById("react-app")
);
}
reducers
const actionTypes = require('../actions/actionTypes');
const gantt = {
ganttData: [],
selectedTask: null
};
export default function ganttReducer(state = gantt, action) {
switch (action.type) {
case actionTypes.loadGanttData:
return { ...state, ganttData: [...action.ganttData] };
default:
return state;
}
}
root reducer
import { combineReducers } from 'redux';
import gantt from './ganttReducer';
const rootReducer = combineReducers({
gantt
});
export default rootReducer;
actions
const actionTypes = require('./actionTypes');
export function loadGanttData(ganttData) {
return { type: actionTypes.loadGanttData, ganttData };
}
export function getSelectedTask(ganttTask) {
return { type: actionTypes.setSelectedTask, ganttTask };
}
Error:
Make sure you import your Home component using import Home from '...' as opposed to import { Home } from '...', otherwise you'd be grabbing the unconnected component. In general, I would also avoid exporting the unconnected component.
Change this:
render() {
return (
<div className="fill">
<Gantt data={this.state.data} links={this.state.links} />
</div>
);
}
To
render() {
return (
<div className="fill">
<Gantt data={this.props.data} links={this.state.links} />
</div>
);
}
Your data is comming from your props (redux), not from your state.

Why is my application state not updating (redux)

I am following the redux counter tutorial from the official docs,- but my applications state is seemingly not updating. To be more clear, essentially the application is a counter with an increment button and a decrement button and it displays the current value on the screen.
I can get it to console log the value as it changes, but it doesn't output it on the screen. Can anyone tell me what I'm doing wrong
import React, { Component } from 'react';
import { createStore } from 'redux';
const counter = (state = 0, action) => {
switch(action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state -1;
default:
return state;
}
}
const store = createStore(counter);
store.subscribe(()=>{
console.log(store.getState());
});
class App extends Component {
render() {
return (
<div className="App">
<h1>Counter Application</h1>
<hr/>
<Counter
value={store.getState()}
onIncrement={() => store.dispatch({type: 'INCREMENT'})}
onDecrement={() => store.dispatch({type: 'DECREMENT'})}
/>
</div>
);
}
}
const Counter = ({
value,
onIncrement,
onDecrement
}) => {
return(
<div>
<h1>{value}</h1>
<button onClick={onIncrement}> Plus </button>
<button onClick={onDecrement}> Minus </button>
</div>
)
}
export default App;
You're gonna need the provider and connect components from react-redux
Your App component won't re-render.
AFAIK, simply because a re-render can only be triggered if a component’s state or props has changed.
You need to trigger the your App component re-render inside store subscribe. I see your store subscribe basically do nothing, only logging here.
store.subscribe(()=>{
console.log(store.getState());
});
you could do something like this, to trigger re-render every time redux store updated:
const page = document.getElementById('page');
const render = () => ReactDOM.render(<App />, page);
render();
store.subscribe(render);
The reason:
In your case, the component has no idea about the changes in the redux store and therefore it doesn't re-render.
Components are only re-rendering if they receiv new props/context
or if their local state has updated (as a result of calling setState() in general)
Solution 1 (direct answer to your question, I think)
const Counter = ({ value, onIncrement, onDecrement }) => {
return (
<div>
<h1>{value}</h1>
<button onClick={onIncrement}> Plus</button>
<button onClick={onDecrement}> Minus</button>
</div>
)
};
class App extends Component {
componentDidMount() {
this._unsub = store.subscribe(this._update);
this._update();
}
componentWillUnmount() {
this._unsub();
this._unsub = null;
};
state = { value: undefined };
render() {
const { value } = this.state;
return (
<div className="App">
<h1>Counter Application</h1>
<Counter
value={value}
onIncrement={this._increment}
onDecrement={this._decrement}
/>
</div>
);
}
_decrement = () => store.dispatch({ type: 'DECREMENT' });
_increment = () => store.dispatch({ type: 'INCREMENT' });
_update = () => {
const value = store.getState();
this.setState({ value });
}
}
Solution 2 (the correct one)
Use react-redux module
Also check these:
- normalizr
- normalizr + keyWindow concept talk
- Reselect
- ComputingDerivedData Post
- react-reselect-and-redux post
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { _valueSelector1, _valueSelector2 } from 'app/feature/selectors';
import { increment, decrement } from 'app/feature/actions';
const mapStateToProps = (state, props) => ({
valueOne: _valueSelector1(state, props),
valueTwo: _valueSelector2(state, props),
})
const mapDispatchToProps = {
increment,
decrement,
};
#connect(mapStateToProps, mapDispatchToProps)
export class YourComponent extends Component {
static propTypes = {
valueOne: PropTypes.number,
valueTwo: PropTypes.number,
increment: PropTypes.func.isRequired,
decrement: PropTypes.func.isRequired,
}
render() {
const { valueOne, valueTwo, increment, decrement } = this.props;
return (
<div>
<span>{valueOne}</span>
<Counter value={valueTwo} onIncrement={increment} onDecrement={decrement} />
</div>
)
}
}

Using mapStateToProps in Redux

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.

React Redux - changes aren't reflected in component

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

Categories