controlled range-slider component in react with redux - javascript

i am learning redux with react and trying to create an app where i have a range-slider, whose value dictates how many Box components will be rendered on the screen.
i am trying to make the range-slider a controlled component but can't make it change the store. i am getting no errors.
the component:
import React from 'react';
import { connect } from 'react-redux';
import { setBoxNumber } from '../actions/actions';
const Slider = ({ boxNumber, handleChange }) => {
return(
<div>
<div>
{boxNumber}
</div>
<div>
<input
onChange={handleChange}
value={boxNumber}
type="range"
min="12"
max="480"
/>
</div>
</div>
)
}
const mapStateToProps = (state) => {
return { boxNumber: state.boxNumber }
}
const mapDispatchToProps = (dispatch) => {
return {
handleChange: (event) => dispatch(setBoxNumber(event.target.value))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Slider);
the reducer:
import { combineReducers } from 'redux';
export const boxNumberReducer = (boxNumber = 40, action) => {
switch(action.payload) {
case 'SET_BOX_NUMBER':
return action.payload;
default:
return boxNumber;
}
}
export default combineReducers({
boxNumber: boxNumberReducer
})
the action:
export const setBoxNumber = (number) => {
return {
type: 'SET_BOX_NUMBER',
payload: number
}
}
i also tried to call the handleChange method with an arrow function on change, like i would do with a controlled react component without redux, but it's making no difference

I think your reducer is configured incorrectly. You can pass all the initial states inside the variable initialState like this.
//reducer.js
import { combineReducers } from "redux";
const initialState = {
boxNumber: 40,
};
const boxReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_BOX_NUMBER":
return {
...state,
boxNumber: action.payload,
};
default:
return state;
}
};
export default combineReducers({
boxReducer,
});
This is how your index.js file should look like:
//index.js
import React from "react";
import ReactDOM from "react-dom";
import Slider from "./Slider.js";
import { Provider } from "react-redux";
import { createStore } from "redux";
import reducer from "./redux/reducer";
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<Slider />
</Provider>,
document.getElementById("root")
);
You need to update your mapStateToProps in Slider.js to access the states in your reducer.
//Slider.js
const mapStateToProps = (state) => {
return { boxNumber: state.boxReducer.boxNumber };
};
This is a simple fix. As your app gets bigger, you'll need more reducers and thus it's better to keep a separate file for that.

Related

Props returning undefined when using redux store

I have started learning react-redux and was trying out the same but somehow the props are getting returned as undefined after mapping. Sharing code flow:
Below is the detailed code giving a brief idea on each component used.
App.js:
import './App.css';
import CakeContainer from './redux/cakes/CakeContainer';
import React from 'react';
import { Provider } from 'react-redux';
import store from './redux/store';
function App() {
console.log(store.getState())
return (
<Provider store = {store}>
<div className="App">
<CakeContainer/>
</div>
</Provider>
);
}
export default App;
CakeContainer.js
import React from 'react'
import { connect } from 'react-redux'
import { buyCake } from './CakeAction'
function CakeContainer(props) {
return (
<div>
<h1>Cake Container !!</h1>
<h2>Number of cakes - {props.cake}</h2>
<button onClick = {props.buyCake}> buy cakes</button>
</div>
)
}
const mapStateToProps = (state) =>{
return {
cake: state.cakeQuant
}
}
const mapDispatchToProps = dispatch => {
return {
buyCake: () => dispatch(buyCake())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CakeContainer)
Action.js
import {BUY_CAKE} from './CakeTypes'
export const buyCake = () =>{
return{
type: 'BUY_CAKE'
}
}
Reducer:
Reducer where i am passing the initial state and action for further processing.
import { BUY_CAKE } from "./CakeTypes";
const initialState = {
cakeQunt : 10
}
const CakeReducer = (state = initialState, action)=>{
switch(action.type){
case 'BUY_CAKE': return {
...state,
cakeQunt: state.cakeQunt - 1
}
default: return state;
}
}
export default CakeReducer;
Store.js
Creating store and passing reducer details to it
[![import { createStore } from "redux";
import CakeReducer from './cakes/CakeReducer'
const store = createStore(CakeReducer,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
console.log(store.getState())
export default store;][1]][1]
Image showing the props value as undefined:
[1]: https://i.stack.imgur.com/EjXU1.png

I can't fetch the data from reducer to component

I'm trying pass the data from reducer to component and receive as props.
But the data return UNDEFÄ°NED, so I have tried console the data on reducer and action, but it's okey. There isn't any problem with the data coming from the API, but it always return to component undefined. Where is my fault?
Action
export default ProfileTab;
import axios from 'axios';
import { BASE, API_KEY } from '../config/env';
export const FETCHED_MOVIES = 'FETCHED_MOVIES';
export function fetchMovies() {
return (dispatch) => {
axios
.get(`${BASE}s=pokemon&apikey=${API_KEY}`)
.then((result) => result.data)
.then((data) =>
dispatch({
type: FETCHED_MOVIES,
payload: data.Search,
}),
);
};
}
Reducer
import { FETCHED_MOVIES } from '../actions/movies';
const initialState = {
fetching: false,
fetched: false,
movies: [],
error: {},
};
export default (state = initialState, action) => {
switch (action.type) {
case 'FETCHED_MOVIES':
return {
...state,
movies: action.payload,
};
default:
return state;
}
};
Component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { fetchMovies } from '../../actions/movies';
class Case extends Component {
static propTypes = {
movies: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
}
componentDidMount() {
this.props.fetchMovies();
}
onChangeHandler = (e) => {
this.setState({
input: e.target.value,
});
};
render() {
console.log(this.props.movies);
return (
<div>
<div className="movies-root">
<div className="movies-wrapper">
<div className="movies-container safe-area">
<h1>mert</h1>
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
movies: state.movies,
};
};
const mapDispatchToProps = {
fetchMovies,
};
export default connect(mapStateToProps, mapDispatchToProps)(Case);
Do this in the connect statement:
export default connect(mapStateToProps,{fetchMovies})(Case);
And remove the mapDispatchToProps function from your code.
Dispatching props as an object is quite incorrect. Try this, and it should work.
That's because your mapDispatchToProps function should return an object and take dispatch as parameter. Each field in your returned object should contain a function that dispatches your action.
So try something like this:
const mapDispatchToProps = dispatch => {
return {
fetchMovies: () => dispatch(fetchMovies())
}
}
Although there's already an accepted answer, I'm not sure how correct it is, as it's completely valid to pass mapDispatchToProps the way you did with the latest react (16.13.1) and react-redux (7.2.1) versions (I'm not sure about earlier versions).
Now, assuming your question contains the whole code, there are two important things missing:
Creating the store:
import { createStore } from "redux";
const store = createStore(reducer);
and passing it to the Provider component:
<Provider store={store}>
If you go ahead and do as above, you'll see that this.props.fetchMovies emits the following error:
Actions must be plain objects. Use custom middleware for async actions.
To fix it, do as it says and add a middleware, e.g. thunk:
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
const store = createStore(rootReducer, applyMiddleware(thunk));
What follows is the full code. Note that I "split" fetchMovies into two functions: sync and async, for illustrating the difference usage between the two. I also modified your code (made is shorter, mostly) for this answer's readability. You can also see a live demo here:
File app.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchMoviesSync, fetchMoviesAsyncMock } from "./api";
class App extends Component {
componentDidMount() {
this.props.fetchMoviesSync();
this.props.fetchMoviesAsyncMock();
}
render() {
return (
<div>
<div className="movies-root">
<div className="movies-wrapper">
<div className="movies-container safe-area">
{this.props.movies.join("\n")}
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = (state) => ({ movies: state.movies });
const mapDispatchToProps = {
fetchMoviesSync,
fetchMoviesAsyncMock
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
File api.js
export const FETCHED_MOVIES = "FETCHED_MOVIES";
export const fetchMoviesSync = () => ({
type: FETCHED_MOVIES,
payload: ["movie1", "movie2", "movie3", "movie4"]
});
export const fetchMoviesAsyncMock = () => (dispatch) => {
dispatch({
type: FETCHED_MOVIES,
payload: ["movie5", "movie6", "movie7", "movie8"]
});
};
File reducer.js
const initialState = {
movies: [],
};
export default (state = initialState, action) => {
switch (action.type) {
case "FETCHED_MOVIES":
return {
...state,
movies: state.movies.concat(action.payload)
};
default:
return state;
}
};
File index.js
import React from "react";
import ReactDOM from "react-dom";
import Case from "./app";
import reducer from "./reducer";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
let store = createStore(reducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<Case />
</Provider>,
document.getElementById("container")
);
File index.html
<body>
<div id="container"></div>
</body>

react redux action not calling reducer

I cant for the life of my get one of my actions to call the reducer.
I've written multiple other actions and reducers in this app, which works perfectly.
It is the beginning of a filter function. I have a text input field, where I constantly keep track of the input field state, and dispatch the field value to the redux action. I have a console.log inside the action, which logs every keypress properly.
What I cant seem to understand, is why the reducer isn't called at each keypress. I've tried multiple console.log's inside the reducer, however none of them gets logged with keypresses.
The first console.log inside the reducer is called when I refresh the page.
if I try to log action.type instead, I get:
##redux/PROBE_UNKNOWN_ACTION1.0.i.0.0.9
If I try the same in any of the other reducers I've written in the same app, I get the appropriate type logged out.
Some code:
Filter Component:
import React, { useState } from 'react'
import { useDispatch } from 'react-redux';
import { filterAnecdotes } from '../reducers/filterReducer';
const Filter = () => {
const [value, setValue] = useState("");
const handleChange = (e) => {
setValue(e.target.value)
}
useDispatch(filterAnecdotes(value));
const style = {
marginBottom: 10
}
return (
<div style={style}>
filter <input onChange={handleChange} />
</div>
)
}
export default Filter
Reducer and action:
Here, I haven't figured out how to get the state of all anecdotes, and what to actually return. For now, I'm just trying to have it get called properly.
const filterReducer = (state = null, action) => {
// These logs only get logged on refresh.
// The action.type should be 'SEARCH', but is not.
console.log("From filterReducer")
console.log(action.type)
switch(action.type) {
case 'SEARCH':
// This is not called at all.
console.log(action.type, action.data)
return action.data;
default:
return state
}
}
export const filterAnecdotes = (filter) => {
console.log(filter);
return {
type: 'SEARCH',
data: filter
}
}
export default filterReducer;
Example of a redux file that actually works:
const reducer = (state = [], action) => {
console.log(state, action)
switch(action.type){
case 'NEW_ENTRY_NOTIFICATION':
console.log(action.type)
return [...state, action.data]
case 'NEW_VOTE_NOTIFICATION':
return [...state, action.data]
case 'HIDE_NOTIFICATION':
return []
default:
return state
}
}
export const createNewEntryNotification = (notification) => {
return {
type: 'NEW_ENTRY_NOTIFICATION',
data: notification
}
}
export const createNewVoteNotification = (notification) => {
return {
type: 'NEW_VOTE_NOTIFICATION',
data: notification
}
}
export const hideNotification = () => {
return {
type: 'HIDE_NOTIFICATION'
}
}
export default reducer
App component (should be irrelevant)
import React from 'react';
import NewEntry from './components/AnecdoteForm'
import AnecdoteList from './components/AnecdoteList'
import Notification from './components/Notification'
import Filter from './components/Filter';
const App = () => {
return (
<div>
<h2>Anecdotes</h2>
<Filter />
<Notification />
<AnecdoteList />
<NewEntry />
</div>
)
}
store (should be irrelevant)
import anecdoteReducer from './anecdoteReducer';
import notificationReducer from './notificationReducer';
import filterReducer from './filterReducer';
import { combineReducers } from 'redux'
const reducer = combineReducers({
anecdotes: anecdoteReducer,
notifications: notificationReducer,
filters: filterReducer,
});
export default reducer
index.js (should also be irrelevant)
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './App'
import reducer from './reducers/store'
const store = createStore(reducer)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
export default App
I'll be happy to provide more information if needed.
This is for a project from fullstackopen.
Try to instantiate useDispatch like this after const [value, setValue] = useState("");
const dispatch = useDispatch();
And then use the intance to dispatch the action
dispatch(filterAnecdotes(value));
the use of useDispatch is misunderstood.
Link for reference: https://react-redux.js.org/api/hooks#usedispatch
You should create a dispatch from useDispatch:
const dispatch = useDispatch();
dispatch(filterAnecdotes(value));

React Redux not re-rendering when Store changes

So I have been trying to figure this out for a day now.
I think I have set up everything correctly, however, the view does not re-render nor the prop updates. However, I can see the change in Redux Developer tools. I know there are other questions like this on Stackoverflow but none of them really helps me.
Am I not seeing something?
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import Store from './store';
import * as serviceWorker from './serviceWorker';
const store = createStore(Store, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
ReactDOM.render(
<Provider store={store} >
<App />
</Provider>
,
document.getElementById('root'));
//actions.js
const initPurchases = (payload) => {
return {
type: "INITILIZE_PURCHASES",
payload
}
}
module.exports = {
initPurchases,
}
// store.js
const initalState = {
inventory: [],
}
const rootReducer = (state = initalState, action) => {
switch(action.type) {
case "INITILIZE_PURCHASES":
state.purchases = [...action.payload];
break;
default:
return state;
}
return state;
}
export default rootReducer
import React from 'react';
import { connect } from 'react-redux';
import actions from './actions';
class App extends React.Component {
state = {}
componentDidMount = () => {
this.getPurchases();
}
getPurchases = async () => {
// call to api which returns t
this.props.initPurchases(t)
}
render() {
console.log(this.props.purchases) // Returns empty array []
return (
<div className="App">
// Some view
</div>
);
}
}
export default connect(
(state) => {return {purchases: state.purchases}},
actions,
)(App);
Logs from React Redux Developer Tools
Can somebody please help me? I can't figure out what's wrong here. I ommited most of the things that i are not related to my problem (at least I do not think they are). I can upload the entire repo to github to see the bigger context
Your reducer needs to return the new state, otherwise the state remains unchanged:
const rootReducer = (state = initalState, action) => {
switch(action.type) {
case "INITILIZE_PURCHASES":
return { ...state, purchases: [...action.payload] };
break;
default:
return state;
}
return state;
}
I think you need to implement something like:
import actions from './actions'
...
class App extends React.Component {
...
componentDidMount = () => {
this.props.initPurchases();
}
render() {
...
}
}
const mapDispatchToApp = (dispatch) => (
{
initPurchases: () => (
dispatch(actions.initPurchases())
),
}
)
...
export default connect(
(state) => {return {purchases: state.purchases}},
mapDispatchToApp,
)(App);
This is because you need to dispatch actions to the store

Redux state returns as undefined despite Redux devtools correctly displaying store

When console.log'ing state or attempting to render in component, my store returns as undefined. However, in React devtools, the store is showing as expected.
index.js with dummy dispatch calls
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { addItem } from "./actions/actions";
import configureStore from "./store/configure-store";
import DashboardPage from "./components/DashboardPage";
const store = configureStore();
store.dispatch(addItem({ description: "item 1" }));
store.dispatch(addItem({ description: "item 2" }));
store.dispatch(addItem({ description: "item 3" }));
const jsx = (
<Provider store={store}>
<DashboardPage />
</Provider>
);
ReactDOM.render(jsx, document.getElementById('root'));
config-store.js
import { createStore } from 'redux';
import reducer from "../reducers/reducer";
export default () => {
const store = createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
return store;
};
DashboardPage.js
import React from "react";
import { connect } from "react-redux";
const DashboardPage = (props) => {
console.log(props.items); // for debugging
return (
<div>
<h1>Items:</h1>
<p>{props.items}</p>
</div>
)
};
const mapStateToProps = (state) => {
return { items: state.items };
};
export default connect(mapStateToProps)(DashboardPage);
reducer.js
const reducerDefaultState = [];
export default (state = reducerDefaultState, action) => {
switch (action.type) {
case 'ADD_ITEM':
return [
...state,
action.item
];
default:
return state;
}
};
actions.js
export const addItem = (description = "") => ({
type: 'ADD_ITEM',
item: { description }
});
I have studied many resources online relating to this issue, however I can't see where I am differing from suggested implementation.
Your state is an Array which doesn't have items property.
Try using this code:
const mapStateToProps = (state) => {
return { items: state };
};
Or change your reducer/createStore to something like this
import { createStore, combineReducers } from 'redux';
import reducer from "../reducers/reducer";
export default () => {
const store = createStore(
combineReducers({items: reducer}),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
return store;
};
https://redux.js.org/api/combinereducers

Categories