Unable to trigger an update with Redux - reactjs

I am new to redux and I am trying to build a simple Hello World to try out this library. However, I am having trouble with getting the value in the Home component. The two buttons should trigger two different changes. I think the errors must have something to do with the connect method. After hours of research, I still cannot figure out why it does not work. Thank you in advance.
Below is my code:
Home.js -> component
import React from "react";
import { connect } from "react-redux";
import * as actionCreators from "../actions/display.js";
import { bindActionCreators } from "redux";
const Home = props => {
return (
<div>
Message:
<h1>{props.message}</h1>
<button onClick={props.sayHi}>SayHI</button>
<button onClick={props.sayHello}>Say Hello</button>
</div>
);
};
function mapStateToProps(state) {
return { ...state };
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
...actionCreators
},
dispatch
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
App.js
import React from "react";
import { createStore, combineReducers, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import createHistory from "history/createBrowserHistory";
import { Route } from "react-router";
import {
ConnectedRouter,
routerReducer,
routerMiddleware
} from "react-router-redux";
import Home from "./components/Home";
import reducers from "./reducers/reducer"; // Or wherever you keep your reducers
// Create a history of your choosing (we're using a browser history in this case)
const history = createHistory();
// Build the middleware for intercepting and dispatching navigation actions
const middleware = routerMiddleware(history);
// Add the reducer to your store on the `router` key
// Also apply our middleware for navigating
const store = createStore(
combineReducers({
...reducers,
router: routerReducer
}),
applyMiddleware(middleware)
);
const App = () => (
<Provider store={store}>
{/* ConnectedRouter will use the store from Provider automatically */}
<ConnectedRouter history={history}>
<Route path="/" component={Home} />
</ConnectedRouter>
</Provider>
);
export default App;
reducer.js
import { SAY_HELLO, SAY_HI } from "../constants";
const initialState = {
message: "Mark"
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case SAY_HELLO:
return { ...state, message: "Hello Mark" };
case SAY_HI:
return { ...state, message: "Hi Mark" };
default:
return state;
}
};
export default reducer;
actions/display.js
import { SAY_HELLO, SAY_HI } from "../constants";
export const sayHello = () => ({
type: SAY_HELLO
});
export const sayHi = () => ({
type: SAY_HI
});
constants.js
export const SAY_HELLO = "SAY_HELLO";
export const SAY_HI = "SAY_HI";
Update:
I figured a working solution for my code but not an ideal one. I change state=>({message:state.message}) to state=>state which means now my component subscrubes to the global state. I also change{props.message} to {props.defaultmessage} in the hi tag on Home.js. Below is the updated code.
import React from "react";
import { connect } from "react-redux";
import { sayHello, sayHi } from "../actions/display.js";
const Home = props => {
return (
<div>
Message:
{console.log(props.default.message)}
<h1>{props.default.message}</h1>
<button onClick={props.sayHi}>SayHI</button>
<button onClick={props.sayHello}>Say Hello</button>
</div>
);
};
export default connect(state => state, {
sayHello,
sayHi
})(Home);

The problem is in that part of your code:
const store = createStore(
combineReducers({
...reducers,
router: routerReducer
}),
applyMiddleware(middleware)
);
reducers variable contains reducer function, but you are using it as object here.
You should assign your reducer with a specific key in the state, for example data:
const store = createStore(
combineReducers({
data: reducers,
router: routerReducer
}),
applyMiddleware(middleware)
);
Next, message value will be available at state.data path:
function mapStateToProps(state) {
return { message: state.data.message };
}

Hoop it work!
import React from "react";
import { connect } from "react-redux";
import { sayHi, sayHello } from "../actions/display.js";
const Home = props => {
return (
<div>
Message:
<h1>{props.message}</h1>
<button onClick={props.sayHi}>SayHI</button>
<button onClick={props.sayHello}>Say Hello</button>
</div>
);
};
function mapStateToProps(state) {
return { message: state.message };
}
export default connect(mapStateToProps, { sayHi, sayHello })(Home);

Related

React Redux doesn't update UI

React-Redux doesn't update UI when store changes.
I expect {this.props.isLogged} to get changed dynamically when Change button is clicked.
I searched tons of materials but I cannot find why the text doesn't change when button is clicked.
Index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { createStore } from "redux";
import reducers from "./reducers";
import { Provider } from "react-redux";
const store = createStore(
reducers);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
App.js
import React from "react";
import { connect } from "react-redux";
import { loginUser } from "./actions";
class App extends React.Component {
changeText = () => {
this.props.loginUser();
};
render() {
return (
<div>
<span>{this.props.isLogged}</span>
<button onClick={this.changeText}>Change</button>
</div>
);
}
}
const mapStateToProps = (state /*, ownProps*/) => {
return {
isLogged: state.isLogged
};
};
const mapDispatchToProps = { loginUser };
export default connect(mapStateToProps, mapDispatchToProps)(App);
./src/reducers/index.js
import { combineReducers } from "redux";
import { LOGIN_USER } from "../actions";
function userReducer(state = { isLogged: false }, action) {
switch (action.type) {
case LOGIN_USER:
return { isLogged: !state.isLogged };
default:
return state;
}
}
const reducers = combineReducers({
userReducer
});
export default reducers;
./src/actions/index.js
export const LOGIN_USER = "LOGIN_USER";
export function loginUser(text) {
return { type: LOGIN_USER };
}
Check this out..
https://codesandbox.io/s/react-redux-doesnt-update-ui-vj3eh
const reducers = combineReducers({
userReducer //It is actually userReducer: userReducer
});
As you assiging your UserReducer to userReducer prop, you will have to fetch the same way in mapStateToProps
const mapStateToProps = (state /*, ownProps*/) => {
return {
isLogged: state.userReducer.isLogged
};
};
Also, isLogged prop is a Boolean variable.. So you are gonna have to use toString().
<span>{this.props.isLogged.toString()}</span>
Since you are using combineReducers, the isLogged boolean lives in state.userReducer.isLogged.
Consider changing combineReducers to combineReducers({ user: userReducer }) and accessing the flag with state.user.isLogged.

The problem with redux not working on button click

I am new to Redux and was learning how to use it with React. Basically, I did everything correctlyin terms of setting up Redux with react app but when I click on button increment I expect displaying counter to increment by one. But when I do that nothing happens and, certainly, I have checked dispatch and action being sent but basically all is ok. Thus I truly need your help guys here is the code I am using:
index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import registerServiceWorker from "./registerServiceWorker";
import { createStore } from "redux";
import reducer from "./store/reducer";
import { Provider } from "react-redux";
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
registerServiceWorker();
reducer.js
const initialState = {
counter: 0
};
const reducer = (state = initialState, action) => {
if (action.type === "INCREMENT") {
return { counter: state.counter + 1 };
}
return state;
};
export default reducer;
counter.js
import React, { Component } from "react";
import { connect } from "react-redux";
import CounterControl from "../../components/CounterControl/CounterControl";
import CounterOutput from "../../components/CounterOutput/CounterOutput";
class Counter extends Component {
render() {
return (
<div>
<CounterOutput value={this.props.ctr} />
<CounterControl
label="Increment"
clicked={() => this.props.onIncrement}
/>
</div>
);
}
}
const mapStateToProps = state => {
return { ctr: state.counter };
};
const mapDispatchToProps = dispatch => {
return {
onIncrement: () => dispatch({ type: "INCREMENT" })
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
Did you register your reducer with store?
If not, please do that.
I believe this thing should not return string:
const mapStateToProps = state => {
return { ctr: state.counter };
};

How to fix an error "Actions must be plain objects"?

I am new to working with sagas, I can’t solve the problem of "Actions must be plain objects. Use custom middleware for async actions."
I enclose all the necessary code. Already broke his head, solving the issue.
I hope for your help.
I looked at the documentation of the sagas, but did not find anything about this error.
I also watched the react boilerplate, where there are already sagas, but I would like to do this on CRA
action
import { AXIOS } from "../api";
import { takeLatest, put, call } from "redux-saga/effects";
export const GET_GENRES_PENDING = "GENRES::GET_GENRES_PENDING";
export const GET_GENRES_FULFILLED = "GENRES::GET_GENRES_FULFILLED";
export const GET_GENRES_REJECTED = "GENRES::GET_GENRES_REJECTED";
export const getGenresPending = () => ({
type: GET_GENRES_PENDING
});
export const getGenresFulfilled = data => ({
type: GET_GENRES_FULFILLED,
payload: data
});
export const getGenresRejected = error => ({
type: GET_GENRES_REJECTED,
payload: error
});
export function* getGenresAction() {
try {
yield put(getGenresPending());
const data = yield call(() => {
return AXIOS.get(
"/movie/list?api_key=5fcdb863130c33d2cb8f1612b76cbd30&language=ru-RU"
).then(response => {
console.log(response);
});
});
yield put(getGenresFulfilled(data));
} catch (error) {
yield put(getGenresRejected(error));
}
}
export default function* watchFetchGenres() {
yield takeLatest("FETCHED_GENRES", getGenresAction);
}
store
import { applyMiddleware, compose, createStore } from "redux";
import createSagaMiddleware from "redux-saga";
import rootReducer from "./reducers";
import watchFetchGenres from "./actions/getGenresAction";
const sagaMiddleware = createSagaMiddleware();
export function configureStore(initialState) {
const middleware = [sagaMiddleware];
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
initialState,
composeEnhancers(applyMiddleware(...middleware))
);
sagaMiddleware.run(watchFetchGenres);
return store;
}
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import App from "./containers/App";
import * as serviceWorker from "./serviceWorker";
import { configureStore } from "./core/configureStore.js";
const store = configureStore({});
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
serviceWorker.unregister();
App.js
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import MoviesContainer from "./MoviesContainer/MoviesContainer";
import FilterContainer from "./FilterContainer/FilterContainer";
import { Container, GlobalStyle } from "./style.js";
export default function App() {
return (
<Container className="app">
<GlobalStyle />
<Router>
<Route exact path="/" component={FilterContainer} />
<Route path="/movies" component={MoviesContainer} />
</Router>
</Container>
);
}
Container
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import watchFetchGenres from "../../core/actions/getGenresAction";
import Card from "../../components/Card/Card";
import Button from "../../components/Button/Button";
import TextInput from "../../components/TextInput/TextInput";
import { TitleH1, TitleH2, TitleCard } from "../../components/Title/Title";
import { Container, SecondaryContainer } from "../style.js";
class FilterContainer extends React.Component {
// const dispatch = useDispatch();
// useEffect(() => {
// getGenresAction();
// // fetch('https://api.themoviedb.org/3/genre/movie/list?api_key=5fcdb863130c33d2cb8f1612b76cbd30&language=en-US')
// });
componentDidMount() {
this.props.watchFetchGenres();
}
render() {
return (
<Container>
<TitleH1 title="Фильтры" />
<SecondaryContainer>
<TextInput placeholder="Введите название фильма" />
</SecondaryContainer>
<SecondaryContainer filters>
<Card>
<TitleCard title="Фильтр по жанру" />
</Card>
<Card>
<TitleCard title="Фильтр по рейтингу" />
</Card>
<Card>
<TitleCard title="Фильтр по году" />
</Card>
</SecondaryContainer>
<SecondaryContainer>
<Button primary value="Применить фильтры" placeholder="lala" />
</SecondaryContainer>
</Container>
);
}
}
const mapStateToProps = state => ({
genres: state.genres
});
const mapDispatchToProps = dispatch =>
bindActionCreators({ watchFetchGenres }, dispatch);
export default connect(
mapStateToProps,
mapDispatchToProps
)(FilterContainer);
bindActionCreators({ watchFetchGenres }, dispatch);
watchFetchGenres isn't an action creator, so this isn't correct. An action creator is function which returns an action. You have 3 examples of them in your code:
export const getGenresPending = () => ({
type: GET_GENRES_PENDING
});
export const getGenresFulfilled = data => ({
type: GET_GENRES_FULFILLED,
payload: data
});
export const getGenresRejected = error => ({
type: GET_GENRES_REJECTED,
payload: error
});
Those are the types of things you should be binding instead.
Your saga is listening for actions of type "FETCHED_GENRES", so the 3 existing action creators won't work for that. You may need to create another action creator, as in:
export const fetchGenres = () => ({
type: 'FETCHED_GENRES',
});
Then in your mapDispatchToProps, you'll make use of this action creator:
const mapDispatchToProps = dispatch =>
bindActionCreators({ fetchGenres }, dispatch);
And update where you call it:
componentDidMount() {
this.props.fetchGenres();
}

Could not find store in either the context or props of connect site redux error

I am trying to create something similar to todo app with react and redux from here.I have been reading all the solutions for this problem and nothing seems to be working for my case.
Most of the solutions purpose using Provider which I am already using. Any help is much appreciated.
Edit - Few import statement might be missing in snippets, but all components are being imported whenever needed and actions are also defined in action file.
Index.js
import App from './components/App'
import reducer from './reducers/index'
const store = createStore(reducer)
const AppWithStore = (
<Provider store={store}>
<App />
</Provider>
)
ReactDOM.render(AppWithStore, document.getElementById('root'))
Update - Combined Reducer code
import React from 'react'
import { combineReducers } from 'redux'
import TestReducer from './TestReducer'
export default combineReducers({
TestReducer,
})
App.js
import Test from './Test';
class App extends Component {
render() {
return (
<Test />
);}
}
ReactDOM.render(
(<App/>),
document.getElementById("root")
);
export default App
Test.js contains both component and container
import { connect } from 'react-redux'
import { add } from '../actions'
const mapStateToProps = state => ({
todos: state.todos,
})
class Test extends Component {
dosomething() {
const dispatch = this.props;
dispatch(add("New Note"));
}
render() {
return (
<div>
< button OnClick = { this.dosomething.bind(this)} > Test </button>
</div>
)
}
}
export default connect(mapStateToProps)(Test)
The reducer logic for Test is as given below
import React from 'react';
const TestReducer = (state = [], action) => {
const todos = state;
const {type, payload} = action;
switch(action.type){
case 'ADD': {
return {
...state,
todos:"new todo"
}
}
}
return state;
}
export default TestReducer
You should remove
ReactDOM.render(
(<App/>),
document.getElementById("root")
); from App.js file
When you call it again in App.js a new component instance independent of others is created, That's why it is not finding store.As store is not passed to it.
You can check it here https://codesandbox.io/s/vy7wwqw570. As i had remove render api call from app.js it is working now.
you need to import your reducers so that you
if your file name is TestReducer.js
import React from 'react';
export const TestReducer = (state = [], action) => {
const todos = state;
const {type, payload} = action;
switch(action.type){
case 'ADD': {
return {
...state,
todos:"new todo"
}
}
}
return state;
}
then import it in this manner
import {TestReducer} from './TestReducer.js';
const store = createStore(TestReducer);
Try replacing todos: state.todos with todos: state.TestReducer.todos
import { connect } from 'react-redux'
import { add } from '../actions'
const mapStateToProps = state => ({
//todos: state.todos,
todos: state.TestReducer.todos
})
class Test extends Component {
dosomething() {
const dispatch = this.props;
dispatch(add("New Note"));
}
render() {
return (
<div>
< button OnClick = { this.dosomething.bind(this)} > Test </button>
</div>
)
}
}
export default connect(mapStateToProps)(Test)

Unable to implement Redux store in React Native

I'm trying hard to wire redux store in a react-native app but seems like I'm missing something. Any help will be appreciated.
action.js
export const getdata = (data) => {
return (dispatch, getState) => {
dispatch({
type: "GET_DATA",
data
});
};
};
reducer/dataReducer.js
export default (state = {}, action) => {
switch (action.type) {
case GET_DATA:
return { ...state, response: action.data };
default:
return state;
}
};
reducer/index.js
import { combineReducers } from 'redux';
import dataReducer from './dataReducer';
//other imports
export default combineReducers({
data: dataReducer,
//other reducers
});
store/configureStore.js
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducers from './../reducer;
export default function configure(initialState = {}) {
const store = createStore(reducer, initialState, compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : f => f
));
return store;
}
main.js (where I dispatch action)
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import Routes from './Routes';
import configureStore from './store/configureStore';
import { getdata} from './actions';
const store = configureStore();
store.subscribe(() => {
console.log('New state', store.getState); //doesn't update at all
});
class Main extends Component {
componentDidMount() {
store.dispatch(getdata('abc')); //calling action creator
}
render() {
return (
<Provider store={store}>
<Routes />
</Provider>
);
}
}
export default Main;
I also tried wiring Chrome extension to see redux store updates, but no luck there. It always says no store found. How can I get this working?
store can be accessed in the a child class inside the Routes Component by react-redux connect
but here no store in the class but I think You can do the following
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import Routes from './Routes';
import configureStore from './store/configureStore';
import { getdata} from './actions';
const store = configureStore();
store.subscribe(() => {
console.log('New state', store.getState()); //getState() is method
});
store.dispatch(getdata('abc'));
class Main extends Component {
render() {
return (
<Provider store={store}>
<Routes />
</Provider>
);
}
}
export default Main;
You want to dispatch your actions from your container components (aka. smart components, the ones connected to the Redux store). The container components can define props in mapDispatchToProps that let them dispatch actions. I don't have your code, so I'm just gonna assume that your Routes component is the container component that you are connecting to the Redux store. Try something like:
class Routes extends Component {
....
componentDidMount() {
this.props.retrieveData();
}
...
}
const mapStateToProps = state => {
...
};
const mapDispatchToProps = dispatch => {
return {
retrieveData: () => dispatch(getdata('abc'));
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Routes);

Resources