I have a state named isFocused and it is false. When I focus on my input, I want it to turn true. I tried so many things but couldn't handle it. These are what I did.
Initial State
export default {
isFocused: false
}
Action Type
export const SEARCH_ACTION = "SEARCH_ACTION"
Action
export function searchAction() {
return { type: actionTypes.SEARCH_ACTION, payload: null }
}
Reducer
export default function navbarReducer(state = initialState, action) {
switch (action.type) {
case actionTypes.SEARCH_ACTION:
return {
...state,
isFocused: !state.isFocused
}
default:
return state
}
}
UI
focusHandler() {
console.log(this.props.isFocused)
}
<input
placeholder="Search in Facebook"
onFocus={() => this.focusHandler()}
/>
function mapStateToProps(state) {
return {
isFocused: state.NavbarReducer
}
}
export default connect(mapStateToProps)(NavSec1)
It shows always false in console. How can i solve this ? I am new in redux.
mapStateToProps is to read the value from the store and make it accessible in your component.
To change the state in your store you need to dispatch an action, the connect in react-redux takes a second argument called as mapDispatchToProps .
import searchAction from 'your action file path';
focusHandler() {
// dispatch the action
this.props.searchAction();
}
<input
placeholder="Search in Facebook"
onFocus={() => this.focusHandler()}
/>
function mapStateToProps(state) {
return {
isFocused: state.NavbarReducer
}
}
export default connect(mapStateToProps, {searchAction})(NavSec1)
Now onInputFocus you will dispatch an action to the store which toggle's the isFocused state in your store. To check this, You can do
console.log(this.props.isFocused)
inside your render method above your return you should be seeing the state getting changed onFocus.
Related
I wrote a demo to try to understand how "ReactReduxContext.Consumer" works, the main code like this:
Hello.tsx
export default function Hello() {
return <ReactReduxContext.Consumer>
{({store}) => {
return <div>
<ul>
<input value={store.getState().name} onChange={(e) => store.dispatch(changeNameAction(e.target.value))}/>
<div>{JSON.stringify(store.getState())}</div>
</ul>
</div>
}}
</ReactReduxContext.Consumer>
}
Entry.tsx
ReactDOM.render(
<Provider store={store}>
<Hello/>
</Provider>,
document.body
);
State.ts
export type State = {
name: string
};
reducer.ts
const initStore: State = {
name: 'aaa'
}
export default function reducers(state = initStore, action: ChangeNameAction): State {
switch (action.type) {
case 'CHANGE_NAME':
return {
...state,
name: action.name
};
default:
return state;
}
}
action.ts
export type ChangeNameAction = {
type: 'CHANGE_NAME',
name: string,
}
export function changeNameAction(name: string): ChangeNameAction {
return {
type: 'CHANGE_NAME',
name: name
}
}
It renders correctly:
But if I type anything in the text field, the value doesn't change.
I'm not sure why it doesn't work.
Here is a tiny but complete demo of this question: https://github.com/freewind-demos/typescript-react-redux-context-consumer-demo
ReactReduxContext is a React Context that contains the current Redux store.
The store reference never changes in normal operation, which means that anyone listening directly to the context in the manner you are doing will never see updates.
React Redux's connect() and useSelector both subscribe to the store and 'notify' React of the updates (usually by setting some state, which causes a re-render of the subscribed component).
You could implement your own primitive version of useSelector by doing this:
function useSelector(f) {
const store = React.useContext(ReactReduxContent);
const [state, setNextState] = React.useState(() => {
return f(store.getState());
});
React.useEffect(() => {
return store.listen(state => {
setNextState(f(store.getState()));
});
}, [f, store]);
return state;
}
React Context only propagates changes when the value passed to the provider changes, which is why you must subscribe to the store itself - as mentioned earlier, the store will never change in normal operation.
What does work:
Saga pulls the data from an API. The reducer for UPDATE_LOTS fires up and returns the new state.
Redux store is updated with the correct data as can be observed in the chrome extension and through logging.
What doesn't work:
The componentDidUpdate never fires up. Nor does componentWillReceiveProps when replaced by it.
Since the component never received an update, there's no re-rendering either.
Most of the advice on this topic discusses how people accidentally mutate the state, however in my case I don't do that. I've also tried the following construction {...state, lots: action.data} instead of using ImmutableJS's Map.set with no luck.
Any ideas? Not listing the saga files here because that part of the data flow works perfectly.
The component:
class Lots extends React.Component {
constructor(props) {
super(props);
this.props.onFetchLots();
}
componentDidUpdate() {
console.log('updated', this.props.lots);
}
render() {
const lots = this.props.lots;
console.log('render', lots);
return (lots && lots.length) > 0 ? <Tabs data={lots} /> : <div />;
}
}
Mapping and composition:
function mapDispatchToProps(dispatch) {
return {
onFetchLots: () => {
dispatch(fetchLots());
},
};
}
function mapStateToProps(state) {
return {
lots: state.lots,
};
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
const withReducer = injectReducer({ key: 'lots', reducer: lotsReducer });
const withSaga = injectSaga({ key: 'lots', saga });
export default compose(
withReducer,
withSaga,
withConnect,
)(Lots);
Reducer:
export const initialState = fromJS({
lots: false,
});
function lotsReducer(state = initialState, action) {
switch (action.type) {
case FETCH_LOTS:
console.log('fetch lots');
return state;
case UPDATE_LOTS:
console.log('update lots', action.data.data);
return state.set('lots', action.data.data);
default:
return state;
}
}
Everything was correct except for the mapStateToProps function.
Since ImmutableJS was used, I had to access the state property as state.get("lots") instead of state.lots.
Doing so fixed the problem.
My code is here: https://stackblitz.com/edit/react-8g3p3l
There are 2 dumb components:
insertion_citylist.jsx with the following code:
insertion_shoplist.jsx with the following code:
I expect the change on the selected city from the inseriton_citylist.jsx will trigger change on the shop list in insertion_shoplist.jsx. So my container component, app.js contains a function findShops, where the action shoplist(city) is called.
My container component code, app.jsx, is the following:
import React, { Component } from "react";
import { connect } from "react-redux";
import { citylist, shoplist } from "../actions";
import { bindActionCreators } from "redux";
import CityList from "../components/insertion_citylist";
import ShopList from "../components/insertion_shoplist";
class App extends Component {
componentDidMount() {
console.log("mount");
this.props.citylist();
this.props.shoplist();
}
findShops(city) {
console.log("findShops:", city);
this.props.shoplist(city);
}
/* renderShops = shops =>
shops
? shops.map(shop => <option key={shop.id} value={shop.name} />)
: null; */
render() {
console.log("render:" + this.props);
return (
<div>
<CityList data={this.props.data} findShops={this.findShops.bind(this)} />
<ShopList {...this.props} />
{/* <input list="shop" placeholder="shop" />
<datalist id="shop">
{this.renderShops(this.props.shop_data.shops)}
</datalist> */}
</div>
);
}
}
const mapStateToProps = state => {
console.log("map state to props:" + state.shops);
return { data: state.insertion_data }; //Use of spread operator: https://www.youtube.com/watch?v=NCwa_xi0Uuc&t=1721s
//return { city_data: state.cities, shop_data: state.shops };
};
const mapDispathToProps = dispatch => {
console.log("map dispatch to props");
return bindActionCreators({ citylist, shoplist }, dispatch);
};
export default connect(
mapStateToProps,
mapDispathToProps
)(App);
The `findShops` can be called successfully when the city from the downdown list is changed, but seems that the `this.props.shoplist()` just called the action without calling the *reducer*. And the actions code from `actions/index.js` looks like this:
export function citylist() {
return { type: "CITY_LIST", payload: [{city:"Berlin"}, {city: "Frankfurt"}] };
}
export function shoplist(city) {
let data = [{name:"original"},{name:"original 2"}];
if (city === 'Frankfurt') data=[{name:"new"},{name:"new 2"}];
return {
type: "SHOP_LIST",
payload: data
};
}
Problem: The current code is able to trigger the event handler findShops(), but does not succeed in changing the city list state and thus the city list on the insertion_citylist.jsx just keeps unchanged all the while. Could anyone help on this?
Thanks in advance!
it turns out very simple issue
onChange={(e) => props.findShops(e)}
here you are passing e as paramenter
findShops(city) {
this.props.shoplist(city);
}
and using it directly, this is invalid as you will have event in your city parameter.
you need e.target.value
change
<select name="city"
onChange={(e) => props.findShops(e)}>{renderCities(props.data.cities)}</select>
to
<select name="city"
onChange={(e) => props.findShops(e.target.value)}>{renderCities(props.data.cities)}</select>
Demo,
remember react pass an event when you change the value, you need to extract the value when you are reading from it. like you are doing in javascript or jquery.
for checkboxes its e.target.checked and for inputs its e.target.value.
Can someone tell me why state.set is resulting in undefined.
I have a reducer like so:
const initialState = fromJS({
description: 'default_description',
});
function helpReducer(state = initialState, action) {
switch (action.type) {
case DEFAULT_ACTION:
return state;
case CHANGE_DESCRIPTION:
console.log('Change Description reducer action.data: '+action.data); //results in string say foo
console.log('state: '+state); //results in valid state on first call
return state
.set('description':action.data);
default:
return state;
}
}
The first time I call the action CHANGE_DESCRIPTION, state = default_description.
After calling state.set the state.description = undefined.
I have used state.set before, but am unsure why it is failing now.
The action is being called by a child component like so:
Parent:
export class FOO extends React.PureComponent {
constructor(props){
super(props);
}
render() {
return (
<div>
<FOOForm onChange={this.props.changeDescription} />
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return {
dispatch,
changeDescription: (e) => dispatch(changeDescription(e.target.value))
};
}
Foo Form for sake of demonstration:
function fooForm(props){
return <FormControl onChange={props.onChange} componentClass="textarea" />;
}
I'm not sure of the context in which you implement your reducer, but I believe the syntax in immutable JS should make your set look like this:
return state.set('description', action.data)
Note that you need to pass in the property and values as arguments and not as a key value pair.
First, I want to mention that the only thing I'm changing between two approaches is setState vs going through the Redux store. Not changing anything else i.e. components, etc.
If I use the setState approach, I can close my modal but if I go through the store, it doesn't close. Any idea why?
Here's my reducer:
import 'babel-polyfill';
import * as types from '../actions/actionTypes';
const initialState = {
modals: {
"modal1": { isDisplayed: true },
"modal2": { isDisplayed: false }
}
};
export default (state = initialState, action) => {
switch (action.type) {
case types.SET_IS_DISPLAYED_MODAL :
return Object.assign({}, state, {
modals: action.modals
})
default: return state
}
}
}
Here are the two versions of my onClick action that is supposed to close the modal.
This is the setState version and it works:
displayModal(modalId, value)
{
let modals = this.props.modals;
modals[modalId].isDisplayed = value;
return setState({modals: modals});
}
And here's the version that goes through the redux store and it does NOT close my modal.
displayModal(modalId, value)
{
let modals = this.props.modals;
modals[modalId].isDisplayed = value;
return this.props.actions.displayModal(modals);
}
There's not much to the action but here it is:
export const displayModal = (modals) => {
return {
type: types.SET_IS_DISPLAYED_MODAL,
modals
};
}
Just so you see how it looks in my component, here it is:
render() {
return(
<div>
<div>Some info...</div>
{this.props.modals["modal1"].isDisplayed
? <Modal1 />
: null}
{this.props.modals["modal2"].isDisplayed
? <Modal2 />
: null}
</div>
);
}
BTW, I know that I'm hitting the action and the reducer. I also know that if I put a debugger in my mapStateToProps, I'm hitting it with updated state for my modals. So I know both the action and the reducer are doing what they're supposed to.
UPDATE:
I just tried something and this fixed the issue. I added last line to mapStateToProps and updated the section in my component:
function mapStateToProps(state) {
return {
modals: state.modals,
isModal1Displayed: state.modals["modal1"].isDisplayed // Just added this line
}
}
And changed the code in my component to:
render() {
return(
<div>
<div>Some info...</div>
{this.props.isModal1Displayed
? <Modal1 />
: null}
</div>
);
}
First of all, never mutate state in Redux reducer - it must be a pure function to work and detect changes correctly. Same rules apply to objects which you get with props.
You must change your code so you only dispatch an action to the store and reduce it to a new state.
First, dispatch an action:
displayModal(modalId, value)
{
this.props.actions.displayModal(modalId, value);
}
Your action will carry information which modal to hide or show:
export const displayModal = (modalId, value) => {
return {
type: types.SET_IS_DISPLAYED_MODAL,
modalId,
value
};
}
Then you can reduce it:
export default (state = initialState, action) => {
switch (action.type) {
case types.SET_IS_DISPLAYED_MODAL :
return Object.assign({}, state,
{
modals: Object.assign({}, state.modals,
{
[action.modalId]: { isDisplayed: action.value }
})
})
default: return state
}
}
As you can see there is a lot of boilerplate here now. With ES6 and ES7 you can rewrite your reducer with the object spread operator or you can use Immutable.js library, which will help you with setting properties deep in the hierarchy.
Reducer with object spread operator looks like this:
export default (state = initialState, action) => {
switch (action.type) {
case types.SET_IS_DISPLAYED_MODAL :
return {
...state,
modals: {
...state.modals,
[action.modalId]: { isDisplayed: action.value }
}
}
default: return state
}
}
You may ask yourself why your fix works. Let me explain.
You change a modal state when you dispatch an action to the Redux by mutating state in place modals[modalId].isDisplayed = value. After that the action is dispatched, reduced and mapToProps gets called again. There is probably reference check in connect higher order component and you have mutated the modal but not the modals object so it has the same reference = your component doesn't re-render. By adding a isModal1Displayed field you are actually disabling optimizations because there is boolean comparison, not a reference check and your component rerenders.
I hope it will help you with understanding Redux and it's principles.