I've been getting an undefined in home.js when I try to console.log(products)
I tried to use postman, and it can successfully get the data from the database, also inside apicalls.js, when I try to console.log(res.data).
Am I missing something? I tried to copy in the tutorial, but it didn't work for me.
apicalls.js
import axios from 'axios'
import {
getProducstStart,
getProductsFailure,
getProductsSuccess,
} from './ProductAction'
export const getProducts = async (dispatch) => {
dispatch(getProducstStart())
try {
const res = await axios.get('/product')
dispatch(getProductsSuccess(res.data))
} catch (error) {
dispatch(getProductsFailure())
}
}
home.js
import React, { useContext, useEffect } from 'react'
import { getProducts } from '../../context/ApiCalls'
import { ProductContext } from '../../context/ProductContext'
const Home = () => {
const { products, dispatch } = useContext(ProductContext)
useEffect(() => {
getProducts(dispatch)
}, [dispatch])
console.log(products)
return (
<div className="home">
</div>
)
}
ProducetReducer.js
const ProductReducer = (state, action) => {
switch (action.type) {
case 'GET_PRODUCT_START':
return {
products: [],
isFetching: true,
error: false,
}
case 'GET_PRODUCT_SUCCESS':
return {
products: action.payload,
isFetching: false,
error: false,
}
case 'GET_PRODUCT_FAILURE':
return {
products: [],
isFetching: false,
error: true,
}
default:
return { ...state }
}
}
export default ProductReducer
ProductContext.js
import { createContext, useReducer } from 'react'
import ProductReducer from './ProductReducer'
const INITIAL_STATE = {
products: [],
isFetching: false,
error: false,
}
export const ProductContext = createContext(INITIAL_STATE)
export const ProductContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(ProductReducer, INITIAL_STATE)
return (
<ProductContext.Provider
value={{
products: state.products,
isFetching: state.isFetching,
error: state.error,
dispatch,
}}
>
{children}
</ProductContext.Provider>
)
}
index.js
import { createRoot } from 'react-dom/client'
import App from './App'
import { ProductContextProvider } from './context/ProductContext'
const container = document.getElementById('root')
const root = createRoot(container)
root.render(
<ProductContextProvider>
<App />
</ProductContextProvider>
)
Related
I am using context and reducer API for state management and logic.
The below code returns context provider, which is called in App.js.
Why isn't the component rendering?
SingerState.js
import React, { useReducer } from "react";
import SingerContext from "./singerContext";
import SingerReducer from "./singerReducer";
import { GET_SINGER, SET_LOADING } from "../types";
const SingerState = (props) => {
console.log('foo')
const initialState = {
name: null,
songs: null,
loading: false,
};
const [state, dispatch] = useReducer(SingerReducer, initialState);
const getSinger = () => {
dispatch({
type: GET_SINGER,
payload: {
name: "jb",
songs: ["song1", "song2"],
},
});
};
const setLoading = () => {
dispatch({
type: SET_LOADING,
});
};
return (
<SingerContext.Provider
value={{
name: state.name,
songs: state.songs,
loading: state.loading,
getSinger,
setLoading,
}}
>
{props.childer}
</SingerContext.Provider>
);
};
export default SingerState;
The entire code is in here: https://codesandbox.io/s/heuristic-shockley-9u2xb?file=/src/App.js:0-411
App.js
import "./App.css";
// import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import SingerState from "./context/singer/singerState";
import Navbar from "./components/Navbar";
import Singer from "./components/singer/Singer";
export default function App() {
return (
<div className='App'>
<Navbar />
<SingerState>
<Singer />
</SingerState>
</div>
);
}
singerReducer.js
import { GET_SINGER, SET_LOADING } from "../types";
const reducer = (state, action) => {
switch (action.types) {
case GET_SINGER:
return {
...state,
name: action.payload.name,
song: action.payload.song,
loading: false,
};
case SET_LOADING:
return {
...state,
loading: true,
};
default:
return state;
}
};
export default reducer;
singerContext.js
import { createContext } from "react";
const singerContext = createContext();
export default singerContext;
It should be {props.children} not {props.childer} in SingerState.js. I made this correction inside your Sandbox and it is working. To make sure, I changed initialState to this :
const initialState = {
name: "It's me",
songs: null,
loading: false,
};
You have 2 typos in your code:
1-
It should be {props.children} not {props.childer} in SingerState.js. (Thanks to yousoumar)
And in SingerReducer.js.
Instead of :
switch (action.types) {
case GET_SINGER:
use action.type because types is not defined
Be carefully with typos!!!
I have just completed Learn Redux on Codecademy and want to that knowledge in practice. But I have an error. When I create extraReducers for updating the state to actual promise status it does not add information.
getUserSlice.js
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import { fetchUserInfo } from '../../api';
export const loadUser = createAsyncThunk("getUser/loadUser",
async (arg, thunkAPI) => {
return await fetchUserInfo();
}
});
const sliceOptions = {
name: 'getUser',
initialState: {
info: [],
isLoading: false,
hasError: false,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(loadUser.pending, (state) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(loadUser.fulfilled, (state, action) => {
state.info.push(action.payload)
state.isLoading = false;
state.hasError = false;
})
.addCase(loadUser.rejected, (state, action) => {
state.isLoading = false;
state.hasError = true;
})
},
};
export const getUserSlice = createSlice(sliceOptions);
console.log(getUserSlice);
export const selectUserInfo = (state) => {
console.log(state);
return state;
};
export default getUserSlice.reducer;
api.js
export const fetchUserInfo = async () => {
const user = await fetch('http://localhost:5000/api/user');
const json = user.json();
return json;
}
App.js
import React from 'react';
import './App.css';
import {Container} from 'react-bootstrap';
import Achievement from './components/Achievement/Achievement';
import { useSelector } from 'react-redux';
import { selectUserInfo } from './features/getUser/getUserSlice';
const colors = ['#010626','#4d6396', '#5d1a87', '#5d1a87', '#5d1a87'];
function App() {
let color= colors[0];
const user = useSelector(selectUserInfo)
function changeColor() {
const newColor = `rgb(${Math.round(Math.random() *256)}, ${Math.round(Math.random() *256)}, ${Math.round(Math.random() *256)})`;
color = newColor;
}
return (
<div className="App" style={{ background: color }}>
<Container>
<h1 id="whoAmI">
Witaj na moim portfolio
{user}
</h1>
<button onClick={changeColor}>
Zmień kolor tła
</button>
<div className="col-lg-4 col-md-6 col-sm-12">
<Achievement title="Ukończenie The Web Developer Bootcamp" picture="https://res.cloudinary.com/syberiancats/image/upload/v1630317595/k9g0nox2fyexxawg8whu.jpg" />
</div>
</Container>
</div>
);
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import reportWebVitals from './reportWebVitals';
import { store } from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
reportWebVitals();
store.js
import { configureStore } from "#reduxjs/toolkit";
import getUserReducer from "./features/getUser/getUserSlice";
export const store = configureStore({
reducer: {
getUser: getUserReducer
}
})
Console.log of getUserSlice and state in the selector
Maybe you can use (builder) => {} function in extraReducer and you edit your loadUser because your Api.js already return json like code below:
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import { fetchUserInfo } from '../../api';
export const loadUser = createAsyncThunk('getUser/loadUser', async (arg, thunkAPI) => {
return await fetchUserInfo();
});
const sliceOptions = {
name: 'getUser',
initialState: {
info: [],
isLoading: false,
hasError: false,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(loadUser.pending, (state) => {
state.isLoading = true;
state.hasError = false;
})
.addCase(loadUser.fulfilled, (state, action) => {
state.info.push(action.payload)
state.isLoading = false;
state.hasError = false;
})
.addCase(loadUser.rejected, (state, action) => {
state.isLoading = false;
state.hasError = true;
})
},
};
export const getUserSlice = createSlice(sliceOptions);
console.log(getUserSlice);
export const selectUserInfo = (state) => {
console.log(state);
return state;
};
export default getUserSlice.reducer;
and you maybe forgot to add await before fetch, you should edit your fetching data in api.js into this below:
export const fetchUserInfo = async () => {
const user = await fetch('http://localhost:5000/api/user');
const json = user.json();
return json;
}
You can implicitly return and bypass the Promise result. Something like:
(arg, thunkAPI) => fetchUserInfo();
However, I would take the "by-the-book" way:
export const loadUser = createAsyncThunk("getUser/loadUser",
async (arg, tunkApi) => {
try {
const response = await fetchUserInfo();
return response;
} catch (e) {
return thunkApi.rejectWithValue(e)
}
}
});
I've used useReducer hook in react. al of my code seems right but it isn't working. Here goes my code
import React, { useEffect, useReducer, useContext } from 'react';
import reducer from '../reducers/filter_reducer';
import { useProductsContext } from './products_context';
import { LOAD_PRODUCTS } from '../actions';
const FilterContext = React.createContext();
const initialState = {
filtered_products: [],
};
const FilterProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const { listOfProducts } = useProductsContext();
useEffect(() => {
dispatch({ type: LOAD_PRODUCTS, payload: listOfProducts });
}, [listOfProducts]);
return (
<FilterContext.Provider value={{ ...state }}>
{children}
</FilterContext.Provider>
);
};
export const useFilterContext = () => {
return useContext(FilterContext);
};
export default FilterProvider;
and there goes my reducer function :
import { LOAD_PRODUCTS } from '../actions';
const filter_reducer = ({ state, action }) => {
if (action.type === LOAD_PRODUCTS) {
return { ...state, filtered_products: action.payload };
}
};
export default filter_reducer;
It seems my code is right but after running this code it shows this error:
TypeError: Cannot read property 'type' of undefined
Can someone see where I'm wrong?
Try this
const filter_reducer = ( state, action ) => {
if (action.type === LOAD_PRODUCTS) {
return { ...state, filtered_products: action.payload };
}
};
I'm new to redux (and stack-overflow) and am having some issues with my search functionality. Originally it was working fine with data that I manually set, but then I imported Data from an API and I cannot get the search to work now. Any help or advice would be much appreciated!
<-- actions -->
//fetchData.js
import {
FETCH_DATA_REQUEST,
FETCH_DATA_SUCCESS,
FETCH_DATA_FAILURE,
SEARCH_POSTS
} from './types';
export const fetchData = () => {
return (dispatch) => {
dispatch(fetchDataRequest())
axios
.get("https://hn.algolia.com/api/v1/search?query=redux")
.then(response => {
const posts = response.data
dispatch(fetchDataSuccess(posts))
})
.catch(error => {
dispatch(fetchDataFailure(error.message))
})
}
}
export const fetchDataRequest = () => {
return {
type: FETCH_DATA_REQUEST
}
}
const fetchDataSuccess = posts => {
return {
type: FETCH_DATA_SUCCESS,
payload: posts
}
}
const fetchDataFailure = error => {
return {
type: FETCH_DATA_FAILURE,
payload: error
}
}
export function searchData(value) {
return {
type: SEARCH_POSTS,
payload: value
}
}
<--- components --->
//dataContainer.js
import { connect } from 'react-redux';
import { fetchData } from '../actions/fetchData';
import { Card } from 'react-bootstrap';
import { Button } from 'react-bootstrap';
function DataContainer({ results, fetchData }) {
useEffect(() => {
fetchData()
}, [])
return results.loading ? (
<h2>Loading...</h2>
) : results.error ? (
<h2>{results.error}</h2>
) : (
<div>
<h1>Posts</h1>
{results && results.posts && results.posts.map(result =>
<div className ="cardDiv" key={result.objectID}>
<Card>
<Card.Header>By: {result.author}</Card.Header>
<Card.Body>
<Card.Title>{result.title}</Card.Title>
<Card.Text>
{result.body}
</Card.Text>
<Button variant="primary" href={result.url}>See Article</Button>
</Card.Body>
</Card>
{'\n'}
</div>)}
</div>
)
}
const mapStateToProps = state => {
return {
results: state.data
}
}
const mapDispatchToProps = dispatch => {
return {
fetchData: () => dispatch(fetchData())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(DataContainer);
//searchBar.js
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { searchData, fetchData } from '../actions/fetchData';
function SearchBar({ posts, fetchData}) {
useEffect(() => {
fetchData()
}, [])
const { search, value } = posts;
return (
<input
className="form-control mx-auto"
placeholder="Search"
onChange={(e) => searchData(e.target.value)}
value={value}
style={{ maxWidth: '200px', textAlign: 'center' }} />
);
}
function mapStateToProps({ posts, state }) {
return {
posts: state.posts,
value: posts.value
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ searchData, fetchData }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
//homePage.js
import SearchBar from './searchBar';
import DataContainer from './dataContainer';
import { connect } from 'react-redux';
import * as actions from '../actions/fetchData';
class HomePage extends Component {
handleSearchBarSubmit(query) {
this.props.fetchPostsWithQuery(query, () => {
this.props.history.push('/results');
});
}
render() {
return (
<div className="home">
<SearchBar page="home"/>
<DataContainer/>
</div>
);
}
}
export default connect(null, actions)(HomePage);
<--- Reducers --->
//dataReducer
FETCH_DATA_REQUEST,
FETCH_DATA_SUCCESS,
FETCH_DATA_FAILURE,
SEARCH_POSTS
} from "../actions/types";
const _ = require('lodash')
const posts = []
const initState = {
loading: false,
posts,
error: '',
filtered: []
}
const reducer = (state = initState, action) => {
switch (action.type) {
case FETCH_DATA_REQUEST:
return {
...state,
loading: true
}
case FETCH_DATA_SUCCESS:
return {
loading: false,
posts: action.payload.hits,
filtered: action.payload.hits,
error: ''
}
case FETCH_DATA_FAILURE:
return {
loading: false,
posts: [],
error: action.payload
}
case SEARCH_POSTS:
const { payload } = action
const filtered = _.filter(state.data.posts, (o) => _.toLower(o.post).includes(_.toLower(payload)))
return {
...state,
filtered
}
default: return state
}
}
export default reducer;
//searchReducer.js
SEARCH_POSTS
} from '../actions/types';
const posts = []
const INIT_STATE = {
posts
}
export default function (state = INIT_STATE, action) {
switch (action.type) {
case SEARCH_POSTS: {
let { value } = action.payload.hits;
const posts = state.posts.filter((post) => post.title.toLowerCase().includes(state.value.toLowerCase()));
return { ...state, value, posts };
}
default:
return state;
}
}
//rootReducer.js
import { combineReducers } from 'redux';
import { reducer as form } from 'redux-form'
import resultsPosts from './searchReducer';
const rootReducer = combineReducers({
data: dataReducer,
resultsPosts,
form
})
export default rootReducer;
<--- App.js and Index.js --->
//app.js
import './App.css';
import HomePage from './components/homePage';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import ResultPage from './components/resultPage'
function App() {
return (
<BrowserRouter>
<div className='App'>
<h1 style={{textAlign: 'center'}}>Search for Hacker News!</h1>
<Switch>
<Route path="/" exact={true} component={HomePage} />
<Route path='/results/:id' component={ResultPage}/>
</Switch>
</div>
</BrowserRouter>
);
}
export default App;
//index.js
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
After one of the Redux tutorials decided to implement that facny action->reducer->store->view chain for simple app with only login part.
Seems like all setted up but when I run my app - in mapStateToProps(currentState) no any sign of the any custom state fields which I expected to see! (default state from reducer). But the action function is fine, as you can see on the screenshot
I can't see whats wrong here so, decided to ask it.
So here is the code
So, first of all - store.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from '../reducers';
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState, applyMiddleware(thunk));
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
then login reducer
const initialState = {
user: {
name: '',
password: ''
},
fetching: false
}
export default function login(state = initialState, action) {
switch (action.type) {
case LOGIN_REQUEST: {
return { ...state, fetching: true }
}
case LOGIN_SUCCESS: {
return { ...state, user: action.data, fetching: false }
}
case LOGIN_FAIL: {
return { ...state, user: -1, fetching: false }
}
default: {
return state;
}
}
}
and the root (reducers/index.js):
import login from './login/login';
import { combineReducers } from 'redux'
export default combineReducers({
login
});
the action
import {
LOGIN_REQUEST,
LOGIN_SUCCESS,
LOGIN_FAIL
} from '../../constants/login.js';
export function onLoginAttempt(userData) {
return (dispatch) => {
dispatch({
type: LOGIN_REQUEST,
user: userData
})
tryLogin(userData);
}
};
function tryLogin(userData) {
let url = 'SignIn/Login ';
return (dispatch) => {
axios.post(url, userData)
.then((response) => dispatch({
type: LOGIN_SUCCESS,
data: response.data
})).error((response) => dispatch({
type: LOGIN_FAIL,
error: response.error
}))
}
};
So here is entrance point:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './containers/app.js';
import configureStore from './store/configureStore';
let store = createStore(configureStore);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("content")
);
and here is the app.js (Login is just sompe custom div with two fields nothing special)
import React, { Component } from 'react';
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux';
import Login from '../components/login/Login';
import * as pageActions from '../actions/login/login'
class App extends Component {
render() {
const { user, fetching } = this.props;
const { onLoginAttempt } = this.props.pageActions;
return <div>
<Login name={user.name} password={user.password} fetching={fetching} onLoginAttempt={onLoginAttempt} />
</div>
}
}
function mapStateToProps(currentState) {
return {
user: currentState.user,
fetching: currentState.fetching
}
}
function mapDispatchToProps(dispatch) {
return {
pageActions: bindActionCreators(pageActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
I see that you have state which looks like:
reduxState = {
login: {
user: {
name: '',
password: ''
},
fetching: false
}
}
but then you try to access properties that don't exist.
function mapStateToProps(currentState) {
return {
user: currentState.user,
fetching: currentState.fetching
}
}
I think you need to:
function mapStateToProps(currentState) {
return {
user: currentState.login.user,
fetching: currentState.login.fetching
}
}
You have this line let store = createStore(configureStore); on your entry point. However, inside configureStore, you have a call to createStore()
Basically you're calling something like createStore(createStore(reducers)). That's probably the cause of the problem.
You should probably call it like
let store = configureStore( /* pass the initial state */ )