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));
Related
I'm doing a project where the first screen is a simple input to put the name on, and then it goes to another screen (another component) where I need to use the value that the user put in the input earlier to show custom content. I tried to do it with Redux but I'm having difficulties to store the input value in the Redux Store and then use that value in another component. I would like to know how I could store this value and then use it in another component (I honestly have no idea how to do it). If anyone wants, I can also show the other component code.
my first component (where user puts his name):
import React, {useState, useEffect} from "react";
import "../_assets/signup.css";
import "../_assets/App.css";
import { Link } from 'react-router-dom';
function Signup() {
const [name, setName] = useState('')
const [buttonGrey, setButtonGrey] = useState('#cccccc')
useEffect(() => {
if(name!== '') {
setButtonGrey("black")
} else {
setButtonGrey('#cccccc')
}
}, [name])
const handleSubmitForm= (e) => {
e.preventDefault()
store.dispatch({
type: 'SAVE_USER',
payload: name,
})
console.log({name})
}
const handleChangeName = (text) => {
setName(text)
}
return (
<div className="container">
<div className="LoginBox">
<form onSubmit={handleSubmitForm}>
<h2>Welcome to codeleap network</h2>
<text>Please enter your username</text>
<input
type="text"
name="name"
value={name}
onChange = {e => handleChangeName(e.target.value)}
placeholder="Jane Doe"
/>
<div className="button">
<Link to="/main">
<button
type="submit"
style={{backgroundColor: buttonGrey}}
disabled={!name}
>
ENTER
</button>
</Link>
</div>
</form>
</div>
</div>
);
}
export default Signup;
my store.js:
import { createStore } from 'redux';
const reducer = (state= (''), action) => {
switch(action.type) {
case 'SAVE_USER': {
state = {...state, name: action.payload}
}
default: return state
}
}
const store = createStore(reducer)
export {store}
Heading
First, I suggest using Redux-Toolkit. It makes standing up and configuring a React redux store almost too easy.
Here's the quick-start guide
Create/convert to a state slice. When you create a slice you are declaring the name of the slice of state, the actions, and the reducer functions at the same time all at once.
import { createSlice } from "#reduxjs/toolkit";
const userSlice = createSlice({
name: "user",
initialState: "",
reducers: {
saveUser: (state, action) => action.payload
}
});
Create and configure the store.
import { configureStore } from "#reduxjs/toolkit";
import userSlice from "../path/to/userSlice";
const store = configureStore({
reducer: {
user: userSlice.reducer
}
});
Render a Provider and pass the store prop.
import { Provider } from "react-redux";
<Provider store={store}>
... app component ...
</Provider>
From here it's a matter of importing the dispatch function and selecting state, use useDispatch and useSelector from react-redux for this.
Signup
import { useDispatch } from "react-redux";
function Signup() {
const dispatch = useDispatch(); // <-- dispatch function
const navigate = useNavigate();
const [name, setName] = useState("");
const handleSubmitForm = (e) => {
e.preventDefault();
dispatch(userSlice.actions.saveUser(name)); // <-- dispatch the action
navigate("/main");
};
const handleChangeName = (text) => {
setName(text);
};
return (
...
);
}
Example Main component:
import { useSelector } from "react-redux";
const Main = () => {
const user = useSelector((state) => state.user); // <-- select the user state
return (
<>
<h1>Main</h1>
<div>User: {user}</div>
</>
);
};
I am pretty new to Next.JS and I was trying to set up Redux with my Next.JS application. Now my page is supposed to display a list of posts that I am calling in from an API. The page renders perfectly when I'm dispatching from useEffect() to populate the data on to my page, but getStaticProps() or getServerSideProps() are not working whatsoever!
Here is a bit of code that will give you a hint of what I've done so far:
store.js
import { useMemo } from 'react'
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunk from 'redux-thunk'
import rootReducer from './reducers/rootReducer'
const initialState = {}
const middlewares = [thunk]
let store
function initStore(preloadedState = initialState) {
return createStore(
rootReducer,
preloadedState,
composeWithDevTools(applyMiddleware(...middlewares))
)
}
export const initializeStore = (preloadedState) => {
let _store = store ?? initStore(preloadedState)
if (preloadedState && store) {
_store = initStore({
...store.getState(),
...preloadedState,
})
store = undefined
}
if (typeof window === 'undefined') return _store
if (!store) store = _store
return _store
}
export function useStore(initialState) {
const store = useMemo(() => initializeStore(initialState), [initialState])
return store
}
action.js
export const fetchPosts = () => async dispatch => {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts')
dispatch({
type: FETCH_POSTS,
payload: res.data
})
}
_app.js
import { Provider } from 'react-redux'
import { createWrapper } from 'next-redux-wrapper'
import { useStore } from '../redux/store'
export default function MyApp({ Component, pageProps }) {
const store = useStore(pageProps.initialReduxState)
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
These are the files that I needed for the basic redux setup. Once my store was set up and I wrapped my app around the Provider, I initially though of using useEffect() hook to populate data on a component that was rendering inside my index.js file.
component.js
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchPosts } from '../redux/actions/postsAction'
const Posts = () => {
const dispatch = useDispatch()
const { items } = useSelector(state => state.posts)
useEffect(() => {
dispatch(fetchPosts())
}, [])
return (
<div className="">
<h1>Posts</h1>
{items.map(post => {
return (<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>)
})}
</div>
)
}
export default Posts
This worked perfectly! All my posts were showing up inside the component. The problem occurred when I was trying to achieve the same behaviour with server side rendering (or even SSG). I wanted to populate the data during the pre-render phase but for some reason the items array which is supposed to hold all the data is empty, basically meaning that the disptacher was never called! Here is the piece of code that is bothering me (exactly same as previous code, but this time I'm using getStaticProps() instead of useEffect()):
component.js
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { fetchPosts } from '../redux/actions/postsAction'
const Posts = ({ items }) => {
return (
<div className="">
<h1>Posts</h1>
{items.map(post => {
return (<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>)
})}
</div>
)
}
export async function getStaticProps() {
console.log('Props called')
const dispatch = useDispatch()
const { items } = useSelector(state => state.posts)
dispatch(fetchPosts())
console.log(items)
return { props: { items } }
}
export default Posts
By running this, I'm getting an error that items is empty! Please help me, I have no clue what's going wrong here.
Well I fixed this issue myself but I forgot to post an answer for it, my bad!
The problem here really is very simple, hooks don't work outside of a functional component!
I think, inside of getStaticProps just call API or get datas from DB and returns it as props to pages/index.js (any component you want) and inside of this component we can get datas from getStaticProps as props.
Also we can set it as global state using useDispatch of react-redux. After that any component we can call those states using redux mapStateToProps. This is my solution.
This maybe a solution if anyone faced this problem,
import React from 'react';
import {useSelector} from 'react-redux';
import {wrapper} from '../store';
export const getStaticProps = wrapper.getStaticProps(store => ({preview})
=> {
console.log('2. Page.getStaticProps uses the store to dispatch things');
store.dispatch({
type: 'TICK',
payload: 'was set in other page ' + preview,
});
});
// you can also use `connect()` instead of hooks
const Page = () => {
const {tick} = useSelector(state => state);
return <div>{tick}</div>;
};
export default Page;
Got it from here: https://github.com/kirill-konshin/next-redux-wrapper
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.
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
In my reducer, it returns an array of objects that i got from an api. I do a console.log on the list and I'm able to see the array, but when I get access to the reducer in my react class, it shows up as an empty array, why is that so?
Inside the render() function in my react file, it does print for some odd reason, but I have a function where I'm trying to render seperate divs using that data from the reducer and the array shows up empty.
getList() {
let arr = [];
if(this.props.popular){
arr = this.props.popular.map(item => {
return (
<div key={item.id} className="movie">
<img
src={`https://image.tmdb.org/t/p/w300${item.poster_path}`}
//onClick={() => this.displayModal(item)}
/>
</div>)
})
}
// console.log(arr)
// this.props.updateCurrentShowList(arr);
return arr;
}
I use this.props.popular from the mapstatetoprops function i have below.
import { FETCH_POPULAR, RESET_POPULAR } from "../Actions/types";
let initialList = [];
export default function(state = initialList, action){
switch(action.type){
case FETCH_POPULAR:
//return action.payload || false;
initialList = initialList.concat(...action.payload);
//console.log(initialList);
return initialList;
case RESET_POPULAR:
initialList = action.payload;
return initialList;
default:
return state;
}
}
Here the initialList is printed and works and i then return it.
This is my mapStateToProps function that i have in my other file where I want to get access to the array. I used combinereducers in one of my reducers file.
function mapStateToProps(state) {
return {
popular: state.popular
};
}
Why does this.props.popular print correctly when i do it in render(), but whenever i use it anywhere else, it doesnt?
action function
export const fetchPopular = (searchTypeFormat, page) => async (dispatch) => {
let url = `https://api.themoviedb.org/3/discover/${searchTypeFormat}?api_key=${APIKEY}&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=${page}`;
//console.log(url);
const res = await axios.get(url);
//console.log(res.data.results)
dispatch({type: FETCH_POPULAR, payload: res.data.results});
};
my store creation
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import reducers from './Reducers/index';
import reduxThunk from 'redux-thunk';
const store = createStore(reducers, {}, applyMiddleware(reduxThunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
I combined my reducers this way
import { combineReducers } from 'redux';
import authReducer from './authReducer';
import popularReducer from './popularReducer';
import genreListReducer from './genreListReducer';
import searchByGenreReducer from './searchByGenreReducer';
import { reducer as formReducer } from 'redux-form';
import modalReducer from './modalReducer';
import detailsReducer from './moreDetailReducer';
import userDisplayList from './userDisplayList';
export default combineReducers({
auth: authReducer,
form: formReducer,
popular: popularReducer,
genreList: genreListReducer,
searchByGenre: searchByGenreReducer,
modalData: modalReducer,
details: detailsReducer,
displayList: userDisplayList
})
the whole component
import React, { Component } from 'react';
import { withRouter } from "react-router-dom";
import { connect } from 'react-redux';
import * as actions from '../Actions';
class SearchPopular extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
page: 1
}
this.getList = this.getList.bind(this);
}
componentWillMount() {
//console.log(this.props.match.params.format)
this.props.fetchPopular(this.props.match.params.format, this.state.page);
console.log(this.props.popular)
console.log(this.getList());
}
getList() {
let arr = [];
if(this.props.popular){
arr = this.props.popular.map(item => {
return (
<div key={item.id} className="movie">
<img
src={`https://image.tmdb.org/t/p/w300${item.poster_path}`}
//onClick={() => this.displayModal(item)}
/>
</div>)
})
}
//console.log(arr)
// this.props.updateCurrentShowList(arr);
return arr;
}
render() {
console.log(this.props.popular);
return (
<div>
</div>
);
}
}
function mapStateToProps(state) {
return {
popular: state.popular,
updatedList: state.displayList
};
}
export default withRouter(connect(mapStateToProps, actions)(SearchPopular));
You are doing to state update in a wrong way. What you have done is it will always take empty array initially and then append into it.
case 'FETCH_POPULAR':
return [...state, ...action.payload];
Try this in your reducer.
****To your main issue
You are trying to fetch store.popular but you donot have popular in store
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const configureStore = () => {
const store = createStore(
combineReducers({
popular: Your reducer here
}),
composeEnhancer(applyMiddleware(thunk))
);
return store;
}
**** New update
I think that's the issue of function loosing the reference of this.
This is why we are using this.getList.bind(this) in the constructor
So when we call this.getList the function gets the reference of this and can use it. so when you are calling it directly from any other function then use this.getList.bind(this)
componentWillMount() {
//console.log(this.props.match.params.format)
this.props.fetchPopular(this.props.match.params.format, this.state.page);
console.log(this.props.popular)
console.log(this.getList.bind(this));
}
Don't mutate variables in Redux reducers! You'll get lots of weird effects and race conditions. You want to always return fresh new objects from a reducer, unless no action matches in the default case, then return the current state.
So firstly, don't define your initial state with a let and then mutate it in your reducers, that's completely wrong.
Secondly, if you want to return new state based on the previous state, as in your FETCH_POPULAR action, then use the state argument (that's what it's for).
Rewrite like this,
export default function(state = [], action){
switch(action.type){
case FETCH_POPULAR:
return [...state, ...action.payload];
case RESET_POPULAR:
return [];
default:
return state;
}
}