I am using Redux without hooks and all seem to be tied together perfectly, but when I look in the browser console Redux window my state doesn't change. So basically I have a store file which looks like this
import {createStore, applyMiddleware} from "redux";
import thunk from 'redux-thunk'
import {composeWithDevTools} from "redux-devtools-extension/developmentOnly";
import rootReducer from './reducers'
const middleware = [thunk]
const initialState = {}
const store = createStore(rootReducer, initialState,composeWithDevTools(applyMiddleware(...middleware)))
export default store
then I have my global reducer file
import {combineReducers} from "redux";
import searchReducer from './searchReducer'
export default combineReducers({
books: searchReducer
})
and the searchReducers file
import {SEARCH_BOOK, SET_INDEX,FETCH_BOOKS} from "../actions/types";
const initialState = {
query: '',
books: [],
loading: false,
book: []
}
export default function (state = initialState, action) {
switch (action.type) {
case 'SEARCH_BOOK':
return {
...state,
query:action.payload,
loading: false
}
case 'SET_INDEX':
return {
...state,
index:action.payload,
loading: false
}
case 'FETCH_BOOKS':
return {
...state,
index:state.index+40,
books:state.books.concat(action.payload),
loading: false
}
default:
return state
}
}
for now I only have the action type you see imported there
here is that action
import {SEARCH_BOOK} from "./types";
export const searchBook = query => dispatch => {
dispatch ({
type:SEARCH_BOOK,
payload:query
})
}
export const fetchBooks = (query,index) => {
console.log(query)
axios
.get(`https://www.googleapis.com/books/v1/volumes?q=${query}&maxResults=40&orderBy=relevance&startIndex=${index}`)
.then(response =>{
return({
type: FETCH_BOOKS,
payload: response.data.items
})}
)
.catch(err => console.log(err));
};
All is tied together in the App where I imported the provider that wraps up everything.
Here comes the problem. I have a search form that should on change update the query value of the global state
import React, { useState, useReducer} from "react";
import {useSelector, useDispatch} from 'react-redux'
import { Button, Container, Row, Col, Form, FormGroup, FormInput } from "shards-react";
import queryBuilder from "../js/helper";
import style from "./SearchForm/body.module.css";
import {searchBook, fetchBooks} from "../actions/SearchActions";
const initialState = {
title:'',
author:'',
publisher:''
}
function reducer(state,{ field, value }){
return {
...state,
[field]: value
}
}
function SearchForm() {
const index = useSelector(state => state.index)
const [state, dispatch] = useReducer(reducer, initialState);
const [query, setQuery] = useState('');
const disp = useDispatch();
const onChange = e => {
dispatch({ field: e.target.name, value: e.target.value })
}
const { title,author,publisher } = state;
const handleSubmit = e => {
e.preventDefault()
setQuery(queryBuilder(state))
disp(fetchBooks(query, 0))
}
return(
<div>
<Container className={style.FormContainer}>
<Form onSubmit={handleSubmit}>
<Row className={'topBar'}>
<Col>
<FormGroup>
<FormInput id={'bookTitle'} name={'title'} placeholder={'title'} value={title} onChange={onChange}/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<FormInput id={'bookAuthor'} name={'author'} value={author} onChange={onChange} placeholder={'author'}/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<FormInput id={'bookPublisher'} name={'publisher'} value={publisher} onChange={onChange}
placeholder={'publisher'}/>
</FormGroup>
</Col>
<Col>
<Button outline theme='primary' type={'submit'}>Submit</Button>
</Col>
</Row>
</Form>
</Container>
</div>
)
}
export default SearchForm
I don't know what is missing.
Edit
As suggested I tried using hooks and now everything is tied together just fine. The problem is now with the fetching of the books. I updated the action file so you can see the action I added. When I dispatch this action I get this error
Actions must be plain objects. Use custom middleware for async actions
Does anybody know how to fix this?
I guess your error can be resolved as
export const fetchBooks =(query,index) => dispatch => {
console.log(query)
axios
.get(`https://www.googleapis.com/books/v1/volumes?q=${query}&maxResults=40&orderBy=relevance&startIndex=${index}`)
.then(response =>{
dispatch({
type: FETCH_BOOKS,
payload: response.data.items
})}
)
.catch(err => console.log(err));
};
It looks like you're missing a return in your fetchBooks function. You're not returning the promise, which means that the thunk middleware isn't receiving the promise result.
export const fetchBooks = (query,index) => {
console.log(query)
return axios
.get(`https://www.googleapis.com/books/v1/volumes?q=${query}&maxResults=40&orderBy=relevance&startIndex=${index}`)
.then(response =>{
return({
type: FETCH_BOOKS,
payload: response.data.items
})}
)
.catch(err => console.log(err));
};
Related
Help me to understand. i use useDispatch but i don't change the state. what am I doing wrong. I read a lot of information, but I can not understand what is my mistake.
i tried other hooks but nothing works.
I've marked up the code below to make it clear. Reducer, action, store, component
component:
import React from 'react';
import {useDispatch} from "react-redux";
import {loginAction, passwordAction} from "../action/userAction";
import storeConfiguration from "../store/storeConfiguration";
import {changeLogin, changePassword} from "../utils/Const";
const Login = () => {
const dispatch = useDispatch()
const stateUser = storeConfiguration.getState()
const senData = () => {
localStorage.setItem(stateUser.login, stateUser.password)
console.log(storeConfiguration.getState());
let keys = Object.keys(localStorage);
for(let key of keys) {
console.log(`${key}: ${localStorage.getItem(key)}`);
dispatch(loginAction())
dispatch(passwordAction())
}
}
function clear() {
localStorage.clear();
}
return (
<div>
<p>Please, enter Username</p>
<input placeholder={'your login'}
onChange={e => changeLogin( e.target.value)}/>
<p>Please, enter Password</p>
<input placeholder={'your password'}
onChange={e => changePassword( e.target.value)}/>
<p></p>
<button onClick={()=>senData()}>Enter</button>
<button onClick={()=>clear()}>clear</button>
</div>
);
};
export default Login;
action:
export const LOGIN = 'loginAction'
export const PASSWORD = 'passwordAction'
export const loginAction = login =>(
{
type: LOGIN,
payload: login
})
export const passwordAction = password =>(
{
type: PASSWORD,
payload: password
})
reducer:
import {LOGIN, PASSWORD} from "../action/userAction";
function userReducer (state, action)
{
switch (action.type){
case LOGIN:
return {...state, login: action.payload }
case PASSWORD:
return {...state, password: action.payload }
default:
return state
}
}
export default userReducer
store:
import userReducer from "../reducer/userReducer";
import { legacy_createStore as createStore} from 'redux'
const initialState =
{
login:'',
password: '',
}
const store = createStore(userReducer, initialState)
export default store
const:
export const currentLogin = 'Admin'
export const currentPassword = '12345'
export const changeLogin = (login) => {
return login
}
export const changePassword = (password) => {
return password
}
In this two lines of code
dispatch(loginAction())
dispatch(passwordAction())
You haven't passed any payload, so nothing can be changed actually
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 have been working on a little project to better understand react. I recently converted it to use hooks and I am trying to implement redux, with it. However I get the following error now.
TypeError: searchField.toLowerCase is not a function
looking at the docs, I stopped using connect from react-redux and switched to using useDispatch and useSelector. But I believe I have set up everything correctly but not sure as to why this error being raise.
This is my action.js
import { SEARCH_EVENT } from './searchfield_constants';
export const setSearchField = (payload) => ({ type: SEARCH_EVENT, payload });
This is my reducer
import { SEARCH_EVENT } from './searchfield_constants';
const initialState = {
searchField: '',
};
export const searchRobots = (state = initialState, action = {}) => {
switch (action.type) {
case SEARCH_EVENT:
return { ...state, searchField: action.payload };
default:
return state;
}
};
this is my index.js where I am using the Provider from react-redux
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { searchRobots } from './searchfield/searchfield_reducers';
import './styles/index.css';
import App from './App';
const store = createStore(searchRobots);
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
finally here is my App.jsx
import { useState, useEffect, useCallback } from 'react';
import { setSearchField } from './searchfield/searchfield_actions';
import { useDispatch, useSelector } from 'react-redux';
import axios from 'axios';
import React from 'react';
import CardList from './components/CardList';
import SearchBox from './components/SearchBox';
import Scroll from './components/Scroll';
import Error from './components/Error';
import 'tachyons';
import './styles/App.css';
// const mapStateToProps = (state) => ({
// searchField: state.searchField,
// });
// const mapDispatchToProps = (dispatch) => ({
// onSearchChange: (e) => dispatch(setSearchField(e.target.value)),
// });
const App = () => {
const searchField = useSelector(state => state.searchField)
const dispatch = useDispatch();
const [robots, setRobots] = useState([]);
// const [searchField, setSearchField] = useState('');
const fetchUsers = useCallback(async () => {
try {
const result = await axios('//jsonplaceholder.typicode.com/users');
setRobots(result.data);
} catch (error) {
console.log(error);
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
fetchUsers();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const filteredRobots = robots.filter((robot) => {
return robot.name.toLowerCase().includes(searchField.toLowerCase());
});
return !robots.length ? (
<h1 className='f1 tc'>Loading...</h1>
) : (
<div className='App tc'>
<h1 className='f1'>RoboFriends</h1>
<SearchBox searchChange={dispatch(setSearchField(e => e.target.value))} />
<Scroll>
<Error>
<CardList robots={filteredRobots} />
</Error>
</Scroll>
</div>
);
};
export default App;
what am I doing wrong?
So the solution was the following,
I created a function called on searchChange, which calls dispatch and then the setSearchField which uses the e.target.value as the payload.
const onSearchChange = (e) => {
dispatch(setSearchField(e.target.value));
};
so the final return looks like the following
return !robots.length ? (
<h1 className='f1 tc'>Loading...</h1>
) : (
<div className='App tc'>
<h1 className='f1'>RoboFriends</h1>
<SearchBox searchChange={onSearchChange} />
<Scroll>
<Error>
<CardList robots={filteredRobots} />
</Error>
</Scroll>
</div>
);
};
In you App.js, convert this line
const searchField = useSelector(state => state.searchField)
to
const { searchField } = useSelector(state => state.searchField)
basically de-structure out searchField from state.searchField
This is attributed to the fact how redux sets state.
In your reducer searchRobots the initial state provided by redux will be
state = {
...state,
searchField
}
and in this line return { ...state, searchField: action.payload };, you're adding
another property searchField to state.searchField object so you'll need to de-structure it out.
It looks like your searchField value is getting set to undefined or some non-string value.
I found this line to be incorrect
<SearchBox searchChange={dispatch(setSearchField(e => e.target.value))} />
It should be changed to
<SearchBox searchChange={() => dispatch(setSearchField(e => e.target.value))} />
So that on search change this function can be called. Currently you are directly calling dispatch and this may be setting your searchField to undefined
Also for safer side before using toLowerCase() convert it to string ie searchField.toString().toLowerCase()
i'm write react functional component, which should get some data from server. I'm use redux, but i get an error "Cannot read property 'type' of undefined"
Help me please find my mistake
This is my react component, Products.js. I think there may be errors in the export part. Also parent component Products.js (App.js) has wrapped on Provider
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { fetchProducts } from '../actions/productActions';
import { productsReducer } from '../reducers/productReducers'
function Products({ store, products, cartItems, setCartItems }) {
useEffect(() => {
store.dispatch(productsReducer())
})
const [product, setProduct] = useState(null);
return (
<div>
{
!products
? (<div>Loading...</div>)
:
(<ul className="products">
{products.map(product => (
<li key={product._id}>
<div className="product">
<a href={"#" + product._id}>
<img src={product.image} alt={product.title} />
<p>{product.title}</p>
</a>
<div className="product-price">
<div>${product.price}</div>
<button>Add To Cart</button>
</div>
</div>
</li>
))}
</ul>)
}
</div >
)
}
export default connect((state) => ({ products: state.products.items }), {
fetchProducts,
})(Products);
This is my store.js
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import { productsReducer } from './reducers/productReducers';
const initialState = {};
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
combineReducers({
products: productsReducer,
}),
initialState,
composeEnhancer(applyMiddleware(thunk))
)
export default store;
My reducer, and I am getting error in this file (Cannot read property 'type' of undefined), maybe, I making a transmission error 'action'
import { FETCH_PRODUCTS } from "../types";
export const productsReducer = (state = {}, action) => {
switch (action.type) {
case FETCH_PRODUCTS:
console.log('it work!')
return { items: action.payload };
default:
return state;
}
};
My Action
import { FETCH_PRODUCTS } from "../types";
export const fetchProducts = () => async (dispatch) => {
const res = await fetch("/api/products");
const data = await res.json();
console.log(data);
dispatch({
type: FETCH_PRODUCTS,
payload: data,
});
};
and my Types
export const FETCH_PRODUCTS = "FETCH_PRODUCTS"
This is are study project on redux, but in original teacher writte code on class component. Original code here and here
If i write class component like on source, everything is working, so i think this is a reason of mistake
dispatch function expects redux action as an argument, not reducer. In your case:
import { fetchProducts } from '../actions/productActions';
function Products({ store, products, cartItems, setCartItems }) {
useEffect(() => {
store.dispatch(fetchProducts());
})
...
}
I am learning react-redux, so now I am trying to create react-redux crud app, here is ny solution
Here is repo : repo demo
The button
<span className="delete_info" onClick={() => deleteComment(comment.id) }>Delete</span>
The action creator to delete element
export const removeComment = id =>{
return{
type: ActionTypes.DELETE_COMMENTS,
payload:id
}
}
// delete comments
export const deleteComment = id =>{
console.log('ids', id);
return dispatch =>{
dispatch(fetchCommentsRequest())
axios.delete(`/api/v1/todo/${id}`)
.then(response =>{
console.log('yeees mom', response.data)
dispatch(removeComment(id))
})
.catch(error =>{
const erroMsg =error.message;
console.log('eeeror', erroMsg)
dispatch(fetchCommentsFailure(erroMsg))
})
}
}
Here is my reducer
import * as ActionTypes from '../action-types'
const initialState ={
data:[],
error:'',
comments:[],
loading:false,
editing:false
}
const reducer = (state= initialState, action) => {
switch (action.type) {
case ActionTypes.FETCH_COMMENTS_REQUEST:
return{
...state,
loading: true,
}
case ActionTypes.FETCH_COMMENTS_SUCCESS:
return{
...state,
loading:false,
comments:action.payload,
error:''
}
case ActionTypes.FETCH_COMMENTS_FAILURE:
return{
...state,
loading:false,
error:action.payload
}
case ActionTypes.ADD_COMMENTS:
return{
...state,
comments:state.comments.concat(action.payload)
}
case ActionTypes.DELETE_COMMENTS:
return{
...state,
comments: state.comments.filter(comment =>comment.id !==action.payload)
}
case ActionTypes.EDIT_COMMENTS:
return{
...state,
comments: state.comments.map(comment =>comment.id === action.payload?{
...comment,
editing:!editing
}:comment)
}
default: // need this for default case
return state
}
}
export default reducer
Now when I click delete, I see on the console the ID from action creators, but the element is not removed
and no errors, what is wrong here?
There's a few knots; I made this simplified sandbox (mocking an api call) of how it should work:
https://codesandbox.io/s/wonderful-minsky-99xi4?file=/src/App.js:0-799
index
import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunkMiddleware from "redux-thunk";
import rootReducer from "./rootReducer";
import App from "./App";
const store = createStore(rootReducer, applyMiddleware(thunkMiddleware));
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
App
import React from "react";
import "./styles.css";
import { connect } from "react-redux";
import deleteRequest from "./deleteRequest";
const mapStateToProps = state => {
return {
comments: state.comments
};
};
const mapDispatchToProps = {
deleteRequest: deleteRequest
};
let App = ({ comments, deleteRequest }) => {
const makeDeleteRequest = id => {
deleteRequest(id);
};
return (
<div className="App">
{comments.map(comment => {
return (
<div key={comment.id}>
<p>{comment.text}</p>
<button onClick={() => makeDeleteRequest(comment.id)}>
Delete
</button>
</div>
);
})}
</div>
);
};
App = connect(
mapStateToProps,
mapDispatchToProps
)(App);
export default App;
reducer
const initialState = {
data: [],
error: "",
comments: [{ id: 1, text: "test1" }, { id: 2, text: "test2" }],
loading: false,
editing: false
};
function rootReducer(state = initialState, action) {
switch (action.type) {
case "DELETE_COMMENT":
return {
...state,
comments: state.comments.filter(comment => comment.id !== action.id)
};
default:
return state;
}
}
export default rootReducer;
async action
import deleteComment from "./deleteComment";
const mockAPI = new Promise(function(resolve, reject) {
setTimeout(() => resolve("deleted"), 2000);
});
const deleteRequest = id => {
return dispatch => {
const makeDeleteRequest = async () => {
await mockAPI;
dispatch(deleteComment(id));
};
makeDeleteRequest();
};
};
export default deleteRequest;
delete comment action
export default function deleteComment(id) {
return { type: "DELETE_COMMENT", id };
}
It looks like you're not actually dispatching the action, you're just returning an async action creator (deleteComment function). In order your code to work, you need to first add redux-thunk middleware to your redux store (so that you can use async action creators) and then, in your component, when you're calling deleteComponent, you have to wrap the call using redux dispatch.
If you're using a function component, you can add useDispatch hook and have something like:
import {useDispatch} from "react-redux";
// ...
function MyComponent() {
const dispatch = useDispatch();
// ...
return <span className="delete_info" onClick={() => dispatch(deleteComment(comment.id))}>Delete</span>
}
or you can just use the connect function to create a HOC and pass the dispatch function from the provider's context:
const ConnectedComponent = connect(undefined, dispatch => ({dispatch}))(MyComponent);
function MyComponent({dispatch}) {
return <span className="delete_info" onClick={() => dispatch(deleteComment(comment.id))}>Delete</span>;
}