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();
Related
I am using redux-toolkit to acquire accessToken from MSAL and redux-persist to persist the store in localStorage. I'm getting search results in clientlisting page. When I refresh the page it was working fine. But few minutes ago it throws me an error "Error in function eval in ./node_modules/redux-persist/es/persistReducer.js:144 baseReducer is not a function" I couldn't figure where am I doing wrong
store.js
import { configureStore } from '#reduxjs/toolkit'
import usersReducer from "./userSlice";
import storage from 'redux-persist/lib/storage';
import { persistReducer, persistStore } from 'redux-persist';
const persistConfig = { key: 'root', storage, }
const persistedReducer = persistReducer(persistConfig, usersReducer)
export const store = configureStore(
{
reducer: {
users: persistedReducer,
}
})
export const persistor = persistStore(store)
userSlice.js
import { useMsal } from "#azure/msal-react";
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { loginRequest } from "./authConfig";
import { msalInstance } from "./pages/index";
export const fetchUsersToken = createAsyncThunk(
"users/fetchUsersToken",
async (dispatch, getState) => {
try {
const token = await msalInstance.acquireTokenSilent(dispatch)
.then((data) => data.accessToken)
return token
} catch (error) {
return error.response.data
}
}
);
const usersSlice = createSlice({
name: "users",
initialState: {
users: null,
loading: true
},
reducers: {},
extraReducers(builder) {
builder
.addCase(fetchUsersToken.pending, (state, action) => {
state.loading = true
})
.addCase(fetchUsersToken.fulfilled, (state, action) => {
state.loading = false,
state.users = action.payload
})
.addCase(fetchUsersToken.rejected, (state, action) => {
state.loading = false
});
}
})
export default usersSlice.reducer;
index.js
import React from "react"
import { Provider, useDispatch } from "react-redux";
import {persistor, store} from "../../store";
import Footer from "../Footer"
import { createTheme, ThemeProvider } from "#mui/material/styles"
import { PersistGate } from 'redux-persist/integration/react';
// Global styles and component-specific styles.
//For changing default blue color for mui text-fields
const theme = createTheme({
palette: {
primary: { main: "#000000" },
},
})
const Layout = ({ children }) => (
<div>
<ThemeProvider theme={theme}>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
{children}
<Footer/>
</PersistGate>
</Provider>
</ThemeProvider>
</div>
)
export default Layout
LandingPage.js ( where I'm dispatching the action.)
const request = {
...loginRequest,
account: accounts[0]
}
store.dispatch(fetchUsersToken(request))
Here is my index.js ( where msalInstance initiated )
import React from "react"
import { Helmet } from "react-helmet"
import { PublicClientApplication } from "#azure/msal-browser"
import { MsalProvider, useMsal } from "#azure/msal-react"
import { loginRequest, msalConfig } from "../authConfig"
import PageLayout from "../components/PageLayout"
import App from "./app"
import Layout from "../components/Layout"
//Redux
import { Provider, useDispatch } from "react-redux";
import {store} from "../store";
//Redux Ends here
export const msalInstance = new PublicClientApplication(msalConfig)
export default function Home() {
return (
<>
<Helmet>
<title>Client Engagement Lookup</title>
</Helmet>
<MsalProvider instance={msalInstance}>
{/* <Provider store={store}> */}
<Layout>
<PageLayout />
</Layout>
{/* </Provider> */}
</MsalProvider>
</>
)
}
After copy/pasting the code you shared into a running codesandbox I wasn't able to reproduce the error you describe, but I do see some discrepancies in the code, specifically in the userSlice.js file.
The main discrepancy I see is that the thunk is incorrectly accessing the thunkAPI. createAsyncThunk payload creators do take two arguments, the first is the arg (e.g. the request object) that is passed to the function and the second is the thunkAPI object. Update the thunk to correctly destructure dispatch and getState from the thunkAPI object.
export const fetchUsersToken = createAsyncThunk(
"users/fetchUsersToken",
async (request, { dispatch, getState }) => { // <-- destructure thunkAPI
try {
const { accessToken } = await msalInstance.acquireTokenSilent(request);
return accessToken;
} catch (error) {
return error.response.data;
}
}
);
A second discrepancy I noticed was in the fetchUsersToken.fulfilled reducer case where a Comma operator was used between the lines to set the loading and users states. This doesn't really effect much though since each operand mutates the state independently, but should still be fixed for readability's and maintenance's sake.
const usersSlice = createSlice({
name: "users",
initialState: {
users: null,
loading: true
},
extraReducers(builder) {
builder
.addCase(fetchUsersToken.pending, (state, action) => {
state.loading = true;
})
.addCase(fetchUsersToken.fulfilled, (state, action) => {
state.loading = false; // <-- own line, expression
state.users = action.payload; // <-- own line, expression
})
.addCase(fetchUsersToken.rejected, (state, action) => {
state.loading = false;
});
}
});
export default usersSlice.reducer;
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>
)
I want to remove item(product) in React-Reduxenter code here
ProdcutType.js
export const ADD_TO_CART = 'ADD_TO_CART'
export const REMOVE_ITEM = 'REMOVE_ITEM'
ProdcutAction.js
import { ADD_TO_CART, REMOVE_ITEM } from "./ProductType";
export const add_to_cart = (img, title, price) => {
return {
type: ADD_TO_CART,
img, title, price
}
}
export const remove_item = (index) => {
return {
type: REMOVE_ITEM,
index
}
}
ProductReducer.js
import { ADD_TO_CART, REMOVE_ITEM } from "./ProductType";
enter code hereconst initialState = {
arrProdcut: [],
}
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TO_CART: return {
...state,
arrProdcut: state.arrProdcut.concat({
img: action.img,
title: action.title,
price: action.price
})
}
case REMOVE_ITEM: {
// const item = action.item;
// const new_state = { ...state };
// delete new_state.arrProdcut[item];
// return new_state;
return {
...state,
arrProdcut: state.arrProdcut.filter(item => item.id != action.index)
}
}
default:
return state;
}
}
export default rootReducer
cart.js
import React, { useEffect } from 'react'
import styled from 'styled-components'
import { useParams } from 'react-router'
import { useSelector } from 'react-redux';
import { remove_item } from './redux/ProductAction'
import { connect } from 'react-redux'
import { Delete } from '#mui/icons-material';
import Cart2 from './Cart2';
function Cart(props) {
const products = useSelector(state => state.arrProdcut)
return (
<Contanier>
<Content>
{products.map((item, index) => (
<Prodcts>
<Cart2 item={item} key={item.id} />
</Prodcts>
))}
</Content>
</Contanier >
)
}
export default Cart
cart2.js
import React, { useEffect } from 'react'
import styled from 'styled-components'
import { useParams } from 'react-router'
import { useSelector } from 'react-redux';
import { remove_item } from './redux/ProductAction'
import { connect } from 'react-redux'
import { Delete } from '#mui/icons-material';
function Cart2(props) {
const { item, index } = props;
return (
<Contanier>
<Content>
<Prodcts>
<div>
<h3>Product</h3>
<Divimg>
<img src={item.img} />
</Divimg>
</div>
<div>
<h3>Name product</h3>
<h4>{item.title}</h4>
</div>
<div>
<h3>Price</h3>
<h4>${item.price}</h4>
</div>
<div>
<h3>Quantity</h3>
<input type="number" />
</div>
<div>
<h3>Remove</h3>
<button onClick={() => props.remove_item(item.id)}>
Delete
</button>
</div>
</Prodcts>
</Content>
</Contanier >
)
}
const MapToDispatch = dispatch => {
return {
remove_item: (id) => dispatch(remove_item(id))
} } export default connect(null, MapToDispatch)(Cart2)
Need some help.
As I am trying to get some understanding of React/REdux global state I made some simple get request.
This is done with Axios, thunk, Redux, but i can't get this working
I have Post.js file, nothing fancy
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import PostForm from './PostForm';
export class Post extends Component {
static propTypes = {
posts: PropTypes.any,
fetchPosts: PropTypes.func,
};
componentDidMount() {
const { fetchPosts } = this.props;
fetchPosts();
}
render() {
const { posts } = this.props;
return (
<div>
<PostForm addPost={this.onSubmit} />
<br />
<div>
{posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
))}
</div>
</div>
);
}
}
export default Post;
Next i have my PostContainer.js
import { connect } from 'react-redux';
import Post from './Post';
import { fetchFromApi } from '../reducers/postReducers';
const mapStateToProps = state => ({
posts: state.posts,
});
const mapDispatchToProps = dispatch => ({
fetchPosts: () => dispatch(fetchFromApi()),
});
export default connect(mapStateToProps, mapDispatchToProps)(Post);
My reducer
import Axios from 'axios';
/* action type */
const FETCH_POSTS = 'FETCH_POSTS';
/* action creator */
export const fetchStarted = payload => ({ payload, type: FETCH_POSTS });
/* thunk */
export const fetchFromApi = () => {
return (dispatch, getState) => {
Axios.get('https://jsonplaceholder.typicode.com/posts?_limit=5').then(res =>
dispatch(fetchStarted(res.data))
);
};
};
/* reducer */
export default function reducer(state = [], action = {}) {
switch (action.type) {
case FETCH_POSTS: {
return {
...state,
data: action.payload,
};
}
default:
return state;
}
}
and my store
import { combineReducers, applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';
import postReducer from './reducers/postReducers';
const initialState = {
posts: {
data: {},
},
};
const reducers = {
posts: postReducer,
};
Object.keys(initialState).forEach(item => {
if (typeof reducers[item] == 'undefined') {
reducers[item] = (state = null) => state;
}
});
const combinedReducers = combineReducers(reducers);
const store = createStore(
combinedReducers,
initialState,
composeWithDevTools(applyMiddleware(thunk))
);
export default store;
All of that is doing not much. My map method is trying to map empty posts object. And for some reason my fetchPosts is not dispatched. I have reade some old posts here but still can't get this working
Thanks
Edit
this is my app.js file with container
import React from 'react';
import './App.css';
import Post from './components/PostContainer';
import { Provider } from 'react-redux';
import store from './store';
function App() {
return (
<Provider store={store}>
<div className='App'>
<Post />
</div>
</Provider>
);
}
export default App;
I managed to get this working.
Data was not there when my posts array was render. After passing simple if statemante all is working
I am trying to combine navigation, more exactly stack navigation with my react native redux application, and I encountered this error during debugging. From what I've already searched, it's because the navigation isn't really compatible with the redux mode of work. So I tried a solution to my problem, but still I have the same error. Here is my code:
Login.js
import React, { Component } from 'react';
import { View, ActivityIndicator, TouchableHighlight } from 'react-native';
import { getLogger, issueToText } from '../core/utils';
import styles from '../core/styles';
import { Card, Button, FormLabel, FormInput } from "react-native-elements";
import { connect } from 'react-redux'
import { loginAction } from '../actions/LoginActions'
class LoginComponent extends Component {
constructor(props) {
super(props);
this.login = this.login.bind(this)
}
render() {
const { error, isLoading } = this.props;
const inputFormProp = {
username: '',
password: ''
};
return (
<View style={{ paddingVertical: 20 }}>
<Card>
<FormLabel>Email</FormLabel>
<FormInput value={inputFormProp.username} onChangeText={(text) => inputFormProp.username = text} />
<FormLabel>Password</FormLabel>
<FormInput value={inputFormProp.password} onChangeText={(text) => inputFormProp.password = text} />
<Button
buttonStyle={{ marginTop: 20 }}
backgroundColor="#03A9F4"
title="SIGN IN"
onPress={this.login(inputFormProp)}
/>
</Card>
<ActivityIndicator animating={this.props.isLoading} style={styles.activityIndicator} size="large" />
</View>
);
}
login(inputFormProp) {
console.log(this.props)
const { store } = this.props.screenProps.store;
console.log(loginAction)
const { dispatch } = this.props
console.log(dispatch)
dispatch(loginAction(inputFormProp))
.then(() => {
if (this.props.error === null && this.props.isLoading === false) {
if (store.getState().auth.token) {
this.props.navigation.navigate('ProductList', { token: store.getState().auth.token });
}
}
})
.catch(error => {
});
}
}
function mapStateToProps(state) {
const { error, isLoading } = state.auth
return {
error,
isLoading,
}
}
export default connect(mapStateToProps)(LoginComponent)
App.js
import React, { Component } from 'react';
import { createStore, applyMiddleware, combineReducers, compose } from 'redux';
import { createLogger } from 'redux-logger';
import thunk from 'redux-thunk';
import { authReducer } from "./src/reducers/LoginReducer";
import { productReducer } from "./src/product/service";
import { ProductList } from "./src/product/ProductList";
import { LoginComponent } from "./src/components/Login";
import { Provider, connect } from "react-redux";
import { StackNavigator, addNavigationHelpers } from "react-navigation";
import Routes from "./src/core/routes";
const AppNavigator = StackNavigator(Routes, {
navigationOptions: {
title: ({ state }) => {
if (state.params) {
return `${state.params.title}`;
}
}
}
});
const navReducer = (state, action) => {
const newState = AppNavigator.router.getStateForAction(action, state);
return newState || state;
};
#connect(state => ({
nav: state.nav
}))
class AppWithNavigationState extends Component {
render() {
return (
<AppNavigator
navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav
})}
/>
);
}
}
const initialState = {
auth: { isLoading: false, error: null },
};
const rootReducer = combineReducers({ product: productReducer, auth: authReducer, nav: navReducer });
let store = createStore(rootReducer, initialState,
compose(applyMiddleware(thunk, createLogger())));
export default function App() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
Routes.js
import { ProductList } from "../product/ProductList";
import { LoginComponent } from "../components/Login";
const Routes = {
Login: { screen: LoginComponent },
ProductList: { screen: ProductList }
};
export default Routes;
Here is my error: Route Login should declare a screen.
What did I do wrong with my code? Thank you.
I fixed the error. It was because I added between LoginComponent {} in the routes.js file at:
import { LoginComponent } from "../components/Login";