The problem shows on Mac OS and it works well on Windows.
It's a demo. When the button is clicked, the number of count did not increase, still 0. But when viewing the state in react dev-tool, the state value `count really increased when the button was clicked. And the reducer is a pure function, when state change, it's just generating a new object.
Below is the code:
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'
// React component
class Counter extends Component {
render() {
const { value, onIncreaseClick } = this.props
return (
<div>
<span>{value}</span>
<button onClick={onIncreaseClick}>Increase</button>
</div>
)
}
}
// Action
const increaseAction = { type: 'increase' }
// Reducer
function counter(state = { count: 0 }, action) {
const count = state.count
switch (action.type) {
case 'increase':
return { count: count + 1 }
default:
return state
}
}
// Store
const store = createStore(counter)
// Map Redux state to component props
function mapStateToProps(state) {
return {
value: state.count
}
}
// Map Redux actions to component props
function mapDispatchToProps(dispatch) {
return {
onIncreaseClick: () => dispatch(increaseAction)
}
}
// Connected Component
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
I think it's simple in the code. But I can not tell why. The problem just happens on Mac OS, both Chrome and Safari.
Could anyone help me?
this code works perfectly. I think there must be some javascript issues which is preventing the code to work, cause could be other files of your project. Please check those or you can share your project code.
Related
This question already has answers here:
How do I access store state in React Redux?
(8 answers)
Closed 1 year ago.
I've created the reducer and using it to change the state of my store. but as you can see in App.js whenever I click on button and update the state. it updates. I can see it in console. but component does not update. as you can see I have list of tracks there it is not updating. and if I make any changes to code because of that the component re-render I can see the new state after that. why is it not rendering automatically whenever the state updates.
Action
import * as actions from './actionTypes'
export const trackAdded = (title, artist, audioSrc, img) => {
return {
type: actions.TRACK_ADDED,
payload: {
title,
artist,
audioSrc,
img
}
}
}
Reducer
import * as actions from './actionTypes'
export default function reducer(state = [], action) {
switch (action.type) {
case actions.TRACK_ADDED:
return [
...state,
{
title: action.payload.title,
artist: action.payload.artist,
audioSrc: action.payload.audioSrc,
img: action.payload.img
}
]
default:
return state
}
}
App.js
import './App.css';
import store from './store'
import { trackAdded } from './actions'
function App() {
const add = (title) => {
store.dispatch(trackAdded(title, "Taylor Swift", "src", "image"))
console.log(store.getState())
}
return (
<div className="App">
{store.getState().map((track, track_id) => {
return (
<li key={track_id}>{track.title}</li>
)
})}
<button onClick={() => { add("Shake It Off") }}>Add Track</button>
</div>
);
}
export default App;
The component will not update because the store.getState() inside of <div className="App"> will only run once when the component is called. There is no logic that exists that tells the component to rerun the store.getState(). If you want the component to receive updates when the store's state changes, you need to connect it to the store using react-redux's connect function or a useSelector hook.
As an example, if using the connect function, you can map the redux state to a react component's props. So if the component's props change, then the component will "react" to it's props changing. The mapping of redux's state to the components props happens in the mapStateToProps function, returning a prop tracks that is mapped to the component. Otherwise there is no reason for the component to update. Also note: in the example below, the store is connected to React through a Provider component, providing the store to child components that wish to connect to it.
import { Provider, connect } from 'react-redux'
import './App.css';
import store from './store'
import { trackAdded } from './actions'
function App(props) {
const add = (title) => {
store.dispatch(trackAdded(title, "Taylor Swift", "src", "image"))
console.log(store.getState())
}
return (
<Provider store={store}>
<div className="App">
{props.tracks.map((track, track_id) => {
return (
<li key={track_id}>{track.title}</li>
)
})}
<button onClick={() => { add("Shake It Off") }}>Add Track</button>
</div>
</Provider>
);
}
const mapStateToProps = (state) => {
const tracks = state
return { tracks }
}
export default connect(mapStateToProps)(App);
Having said that, if you go the connect route, you might want to add a mapDispatchToProps function to the connect so you dont pass around the store everywhere. You can find that info in the redux docs, but that would be an answer to a different question.
This is happening because your component does not know that store has been updated, you can use something like this
useEffect(() => {
this.artistList = store.getState();
}, [store.getState()]); // kind of watcher of store.getState()
but this is definitely not recommended. You would want to use useSelector() from react-redux, which is much concise and recommended way to doing it.
I run into a problem that is litterally blowing my mind.
I'm developing my web application using React and Redux, my application use a system of notification implemented with Firebase.
Every notification is structured as below:
var notification = {
from_user:{
name: 'John',
surname: 'Doe'
},
payload:{
message:'Lorem ipsum ...'
}
seen:false,
timestamp:1569883567
}
After fetched, notification is send to notificationReducer with:
dispatch({type:'FETCH_NOTIFICATION_OK',payload:notification})
And so far everything is ok.
My notificationReducer is structured as below:
const INITIAL_STATE = {
loading:false,
notification:{}
}
const notificationReducer = (state=INITIAL_STATE,action)=>{
switch(action.type){
case 'FETCHING_NOTIFICATION':
return {...state,loading:true}
case 'FETCH_NOTIFICATION_OK':
return {...state,loading:false,notification:action.payload} // I THINK PROBLEM IS HERE
default:
return state
}
}
export default notificationReducer;
The problem is that, when I pass state props to my presentational component, notification object is empty
My presentational component is reported below
import React from 'react';
import {getNotification} from '../actions/actioncreators';
import {connect} from 'react-redux';
class NotificationDetail extends React.Component {
componentWillMount(){
this.props.fetch_notification('9028aff78d78x7hfk');
console.log(this.props.notification) // IT PRINTS: {}
}
render(){
return(
<div>
'TODO'
</div>
)
}
}
const mapStateToProps = state =>{
return {
is_loading:state.notificationReducer.loading,
notification:state.notificationReducer.notification
}
}
const mapDispatchToProps = dispatch =>{
return {
fetch_notification: (id_notification)=>dispatch(getNotification(id_notification))
}
}
export default connect(mapStateToProps,mapDispatchToProps)(NotificationDetail)
Any suggestion ?
EDIT: In my reducer I tried to print the new state. I succesfully got this:
But, Anyway In my presentational component I got an empty object
I think the dispatch call hasn't fired yet. Try executing the below
componentDidMount() {
this.props.fetch_notification();
}
render() {
console.log(this.props.notification); // It should print an output here if your xhr/ajax/axios call is correct
}
Also, using componentWillMount is UNSAFE (according to the ReactJS current documentation). Avoid using this lifecycle in the future.
I just started using typescript so pardon me if I am staring right at the solution. I have a component as follows
import './weatherInfo.css';
import * as React from 'react';
import { connect } from 'react-redux';
import IWeatherInfo from '../../models/WeatherInfo';
import {IApplicationState} from '../../reducers';
import WeatherInfo from './weatherInfoItem';
interface IWeatherInfoList{
weatherInfoList:IWeatherInfo[]
}
class WeatherInfoList extends React.Component<IWeatherInfoList> {
componentWillReceiveProps(){
console.log("Component will recieve props"); <------ 1
}
render() {
console.log(this.props.weatherInfoList);
return (
<div className="weatherInfoContainer">
{this.props.weatherInfoList.map((weatherInfoItem, index)=>{
debugger
return (<WeatherInfo key={index} {...weatherInfoItem}/>);
})}
</div>
);
}
}
function mapStateToProps(state: IApplicationState):IWeatherInfoList {
<------- 2
return {
weatherInfoList: state.weatherInfo.citiesWeatherData
}
}
const mapDispatchToProps = {
};
export default connect(mapStateToProps, mapDispatchToProps)(WeatherInfoList);
state.weatherInfo.citiesWeatherData is of type IWeatherInfo[]
Now whenever I update the store state all is looking good, the store get updated. But the component doesn't get new props (hence it isn't rerendered). So I put a break point at 2 and 1. It stops at 2 but not at 1. I tried changing the type of weatherInfoList to a string array and then it is working. What am I missing here?
I've gone through many of the Redux and ReactJS tuts. I understand setting actions => action creators => dispatch => store => render view (uni-directional flow) with more data substantial events. My problem is dealing with very simple events that change state. I know not all state always needs to be handled in Redux, and that local state events (set on React components) is an acceptable practice. However, technically Redux can handle all state events and this is what I am trying to do.
Here is the issue. I have a React component that renders a Button. This Button has an onClick event that fires a handleClick function. I set the state of the Button via the constructor method to isActive: false. When handleClick fires, setState sets isActive: true. The handleClick method also runs two if statements that, when either evaluate to true, run a function that either changes the background color of the body or the color of paragraph text. Clicking the same button again sets state back to false and will change back the body color or text color to the original value. This Button component is created twice within a separate component, Header. So long story short, I've got two buttons. One changes body color, the other changes p tag color after a click event.
Here's the code for the Button component:
import React, {Component} from 'react';
import {dimLights, invertColor} from '../../../actions/headerButtons';
import { connect } from 'react-redux';
import { Actions } from '../../../reducers/reducer';
const headerButtonWrapper = 'headerButton';
const headerButtonContext = 'hb--ctrls ';
const dimmedLight = '#333333';
const invertedTextColor = '#FFFFFF';
export default class Button extends Component {
constructor (props) {
super(props)
this.state = {
isActive: false
};
}
handleClick (e) {
e.preventDefault();
let active = !this.state.isActive;
this.setState({ isActive: active });
if(this.props.label === "Dim The Lights"){
dimLights('body', dimmedLight);
}
if(this.props.label === "Invert Text Color"){
invertColor('p', invertedTextColor)
}
}
render() {
let hbClasses = headerButtonContext + this.state.isActive;
return (
<div className={headerButtonWrapper}>
<button className={hbClasses} onClick={this.handleClick.bind(this)}>{this.props.label}</button>
</div>
);
}
}
Here's the code for the imported functions that handle changing the colors:
export function dimLights(elem, color) {
let property = document.querySelector(elem);
if (property.className !== 'lightsOn') {
property.style.backgroundColor = color;
property.className = 'lightsOn'
}
else {
property.style.backgroundColor = '#FFFFFF';
property.className = 'lightsOff';
}
}
export function invertColor(elem, textColor) {
let property = document.querySelectorAll(elem), i;
for (i = 0; i < property.length; ++i) {
if (property[i].className !== 'inverted') {
property[i].style.color = textColor;
property[i].className = 'inverted'
} else {
property[i].style.color = '#3B3B3B';
property[i].className = 'notInverted';
}
}
}
Here's the code for the reducers:
import * as types from '../constants/ActionTypes';
const initialState = {
isActive: false
};
export default function Actions(state = initialState, action) {
switch (action.type) {
case types.TOGGLE_LIGHTS:
return [
...state,
{
isActive: true
}
]
default:
return state
}
}
Here's the code for the actions:
import EasyActions from 'redux-easy-actions';
export default EasyActions({
TOGGLE_LIGHTS(type, isActive){
return {type, isActive}
}
})
If it helps, here's the Header component that renders two Button components:
import React, {Component} from 'react';
import Button from './components/Button';
const dimmer = 'titleBar--button__dimmer';
const invert = 'titleBar--button__invert';
export default class Header extends Component {
render() {
return (
<div id="titleBar">
<div className="titleBar--contents">
<div className="titleBar--title">Organizer</div>
<Button className={dimmer} label="Dim The Lights" />
<Button className={invert} label="Invert Text Color" />
</div>
</div>
);
}
}
Finally, here's the code containing the store and connection to Redux (NOTE: Layout contains three main components Header, Hero, and Info. The Buttons are created only within the Header component)
import React, { Component } from 'react';
import { combineReducers } from 'redux';
import { createStore } from 'redux'
import { Provider } from 'react-redux';
import Layout from '../components/Layout';
import * as reducers from '../reducers/reducer';
const reducer = combineReducers(reducers);
const store = createStore(reducer);
// This is dispatch was just a test to try and figure this problem out
store.dispatch({
type: 'TOGGLE_LIGHTS',
isActive: true
})
console.log(store.getState())
export default class Organizer extends Component {
render() {
return (
<Provider store={store}>
<div>
<Layout />
</div>
</Provider>
);
}
}
What I am looking to do is remove the state logic from the local React component and into Redux. I feel like the functions I have imported need to act as dispatchers. I also feel like I am setting up my initial actions incorrectly. This is such an incredibly simple event that finding an answer anywhere online is difficult. Anyone have any thoughts on what I can do to fix this?
You're almost there. It looks like you've left out the code for Layout component, which I assume is the component that's rendering your Button. The critical piece here is going to be your container, which is the component that's wrapped with Redux's connect to link it to the store. Docs for this. More details here.
What you did:
// components/Button.js - pseudocode
import {dimLights, invertColor} from '../../../actions/headerButtons';
handleClick() {
dimLights();
}
What Redux wants you to do instead:
// containers/App.js - pseudocode
import {dimLights, invertColor} from '../../../actions/headerButtons';
class App extends Component {
render() {
// Pass in your button state from the store, as well as
// your connected/dispatch-ified actions.
return (
<Button
state={this.props.buttonState}
onClick={this.props.buttonState ? dimLights : invertColor}
/>
);
}
}
function mapStateToProps(state, ownProps) {
return {
buttonState: state.buttonState
};
}
export default connect(mapStateToProps, {
// Your action functions passed in here get "dispatch-ified"
// and will dispatch Redux actions instead of returning
// { type, payload }-style objects.
dimLights, invertColor
})(App);
Hope that helps! Redux has a lot of boilerplate for simple stuff like this, however, because most of the pieces can be expressed as pure functions, you gain a lot in unit testing flexibility, and get to use the devtools debugger.
I tried to make a reusable component in redux.
The idea behind this is that I am creating a smart combobox and place it several times inside an other component or smart component.
Lets assume the only job from this combobox is to display countries, allow to add new countries and tell the parent what country is selected.
The parent dont have to pass the available countries down to the combobox only the onValueChanged event so the parent knows what country is selected.
This results in the following structure (The items are not really countries to keep it simple but you should get the idea behind it):
//Constants (appConstants.ts)
export const SmartCombobox = {
ADD_ITEM: 'SMART_COMBOBOX/ADD_ITEM'
}
//Action creator (smartComboboxAction.ts)
import { SmartCombobox } from '../constants/appConstants';
export function AddItem() {
return {
type: SmartCombobox.ADD_ITEM
};
}
//Reducer (smartCombobox.ts)
import { SmartCombobox } from '../constants/appConstants';
const initialState = ['Item 1']
export default function items(state = initialState, action) {
switch (action.type) {
case SmartCombobox.ADD_ITEM:
let items = ['Item' + Math.random().toString()]
return state.concat(items);
default:
return state;
}
}
//Container (smartCombobox.ts)
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { default as SmartCombobox } from '../components/combobox';
import * as ComboboxActions from '../actions/smartComboboxAction';
function mapStateToProps(state) {
return {
items: state.items
};
}
function mapDispatchToProps(dispatch) {
return {
comboboxActions: bindActionCreators(<any>ComboboxActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SmartCombobox);
Then I am able to use it like this inside my component or smart component.
When I add a new item, every component that includes my smartCombobox would be synced and has the exact amout of items.
//Component (combobox.tsx)
import * as React from 'react';
interface SmartComboboxProps {
items?: Array<any>,
comboboxActions?: any,
onValueChanged: Function
}
export default class SmartCombobox extends React.Component<SmartComboboxProps, any> {
onValueChanged(event:any) {
let selectedValue = event.target.value;
const { onValueChanged } = this.props;
onValueChanged(selectedValue);
}
componentDidMount() {
// Call value changed for first selected item
this.props.onValueChanged(this.props.items[0]);
}
render() {
const { comboboxActions } = this.props;
let options = this.props.items.map(function (o) {
return <option key={o} value={o}>{o}</option>
});
return (
<div>
<select style={{ width: "200px" }} name="SmartCombobox" onChange={ this.onValueChanged.bind(this) } >
{ options }
</select>
<button onClick={ comboboxActions.AddItem }>Add item</button>
</div>
);
}
}
Final result (Image)
Is this the correct approach for reusable components?
Or are there maybe any pitfalls I might forgot?
There was also the idea that the combobox should be connected directly to an api because the app shouldn't know whats happening in here.
But this would break the idea of flux because I would need a state inside this component etc.
I was against that idea...
Is this the correct approach for reusable components?
Or are there maybe any pitfalls I might forgot
This approach is good.
There was also the idea that the combobox should be connected directly to an api because the app shouldn't know whats happening in here.
You are right here. The source of truth (or rather truth setter) only needs to be one and therefore cannot be the component.