Dispatch is not a function useContext/useReducer React hooks - reactjs

My Home.js component just doesn't seem to see the dispatch function at all. Do you guys know why? I'm kinda new to redux style state management stuff in redux.
I keep getting the error "TypeError: dispatch is not a function"
App.js
import React from 'react';
import { HashRouter, Route } from 'react-router-dom';
import Home from './pages/Home';
import Start from './pages/Start';
import Result from './pages/Result';
import RPSContextProvider from './contexts/RPSContext';
const App = () => {
return (
<HashRouter>
<RPSContextProvider>
<Route exact path="/" component={Home} />
<Route path="/start" component={Start} />
<Route path="/result" component={Result} />
</RPSContextProvider>
</HashRouter>
);
};
export default App;
Home.js
import React, { useRef, useContext } from 'react';
import { RPSContext } from '../contexts/RPSContext';
import './home.css';
const Home = (props) => {
const { state, dispatch } = useContext(RPSContext);
const playerNameEntry = useRef();
const handleClick = () => {
if (!isStringEmpty(playerNameEntry.current.value)) {
dispatch({ type: 'SET_NAME', state: playerNameEntry.current.value });
props.history.push({
pathname: '/start'
});
console.log(dispatch);
}
};
const isStringEmpty = (string) => string.trim().length === 0;
return (
<div className="app-container">
<h1>
You dare battle me at
<br />
Rock, Paper, Scissors?
<br />
You got no chance, kid!
</h1>
<p>What's your name, ya chancer?</p>
<input type="text" onKeyPress={(e) => handleKeyPress(e)} ref={playerNameEntry} />
<button onClick={handleClick}>Start</button>
</div>
);
};
export default Home;
RPSContext.js
import React, { createContext, useReducer } from 'react';
import { RPSReducer } from '../reducers/RPSReducer';
export const RPSContext = createContext();
const RPSContextProvider = (props) => {
const [ state, dispatch ] = useReducer(RPSReducer, { playerName: '' });
return <RPSContext.Provider value={{ state, dispatch }}>{props.children}</RPSContext.Provider>;
};
export default RPSContextProvider;
RPSReducer.js
export const RPSReducer = (state, action) => {
switch (action.type) {
case 'SET_NAME':
return { playerName: action };
default:
throw new Error();
}
};
Basically as a first step I just want to set the name of the entry. I know this is quite a lot of code just for what I'm doing, but just wanting to try out useReducer and useContext so that I can learn all this new stuff in React.

I solved the problem by adding
switch (action.type) {
case 'SET_NAME':
return { ...state, playerName: action.payload }
in my reducer, and in Home.js changed the state key I had in there to payload. Not 100% sure if it having the same name was effecting anything, but its much less confusing naming it payload.
const handleClick = () => {
if (!isStringEmpty(playerNameEntry.current.value)) {
dispatch({ type: 'SET_NAME', payload: playerNameEntry.current.value });

Wrap the whole App with AppContext.Provider passing with state and dispatch, like below
<AppContext.Provider value={{ state, dispatch }}>
<div className="App">
<Compo />
</div>
</AppContext.Provider>

Related

Getting this error "Unexpected empty object pattern" while using react , useStateValue

I am following the WhatsApp clone on YouTube, I did exactly what they were doing but I don't know why I'm getting this error. I was
I read a lot of blogs, but I couldn't resolve it.
In app, it gives this error and couldn't dismiss.
./src/App.js Line 10: 'dispatch' is assigned a value but never used no-unused-vars
In login, it gives this error.
./src/Login.js Line 9: Unexpected empty object pattern no-empty-pattern
<!-- begin snippet: js hide: false console: true babel: false -->
import React from "react";
import "./Login.css";
import { Button } from "#mui/material";
import { auth, provider } from "./firebase";
import { useStateValue } from "./StateProvider";
import { actionTypes } from "./reducer";
function Login() {
const [value, dispatch] = useStateValue({});
// const [value, dispatch] = useStateValue({})
// const [{ type, user }, dispatch] = useStateValue();
const signIn = () => {
auth
.signInWithPopup(provider)
.then((result) => {
dispatch({
type: actionTypes.SET_USER,
user: result.user,
});
})
.catch((error) => alert(error.message));
};
return (
<div className="login">
<div className="login__container">
<img
src="https://www.freepnglogos.com/uploads/whatsapp-logo-png-hd-2.png"
alt=""
/>
<div className="login__text">
<h1>Sign in to WhatsApp</h1>
</div>
<Button onClick={signIn}>Sign In with Google</Button>
</div>
</div>
);
}
export default Login;
import React from "react";
import "./App.css";
import Sidebar from "./Sidebar";
import Chat from "./Chat";
import Login from "./Login";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { useStateValue } from "./StateProvider";
function App() {
const [{ user }, dispatch] = useStateValue();
return (
<div className="app">
{!user ? (
<Login />
) : (
<div className="app__body">
<Router>
<Sidebar />
<Routes>
<Route path="/rooms/:roomId" element={<Chat />} />
<Route path="/" element={<Chat />} />
</Routes>
</Router>
</div>
)}
</div>
);
}
export default App;
import React, { createContext, useContext, useReducer } from "react";
export const StateContext = createContext();
export const StateProvider = ({ reducer, initialState, children }) => (
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
export const initialState = {
user: null,
};
export const actionTypes = {
SET_USER: "SET_USER",
};
const reducer = (state, action) => {
console.log(action);
switch (action.type) {
case actionTypes.SET_USER:
return {
...state,
user: action.user,
};
default:
return state;
}
};
export default reducer;

React JS context with useState

I'm creating a simple example in react js using createContext and useState, but I'm doing something wrong, look it:
this is my component categoriacontex.js
import { createContext } from "react";
const CategoriaContext = createContext();
export default CategoriaContext;
this's component types.js
export const GET_CATEGORIAS = "GET_CATEGORIAS";
this's component categoriasreducer.js
import { GET_CATEGORIAS } from "../types";
export default (state, action) => {
const { payload, type } = action;
;
switch (type) {
case GET_CATEGORIAS:
return {
...state,
categorias: payload,
};
default:
return state;
}
};
this's component categoriastate.js
import React, { useState } from 'react';
import CategoriaContext from './CategoriaContext';
import CategoriaReducer from './CategoriasReducer';
import Data from '../../Data/Categorias.json';
import { GET_CATEGORIAS } from "../types";
const CategoriaState = (props) => {
const initialState = {
categorias: [],
selectedCategoria: null,
};
const [state, setstate] = useState(CategoriaReducer, initialState);
const GetCategorias = () => {
try {
setstate({ type: GET_CATEGORIAS, payload: Data });
} catch (error) {
console.error(error);
}
};
return(
<CategoriaContext.Provider
value={{
categorias: state.categorias
}}
>
{props.children}
</CategoriaContext.Provider>
)
};
export default CategoriaState;
this one is component app.js
import React, { Component } from 'react';
import './App.css';
import Header from './Component/Header/Header';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import CategoriaState from './Context/Categorias/CategoriaState';
import AddCat from './Component/Categorias/AddCat';
import Allcat from './Component/Categorias/AllCat';
class App extends Component {
render(){
return(
<CategoriaState>
<div className="container">
<Router>
<Header />
<Switch>
<Route exact path="/">
<h1>home</h1>
</Route>
<Route path="/addcat">
<AddCat />
</Route>
<Route path="/allcat">
<Allcat />
</Route>
</Switch>
</Router>
</div>
</CategoriaState>
)
}
}
export default App;
and this's componente allcat.js
import React, { useEffect } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import { useContext } from 'react';
import CategoriaContext from '../../Context/Categorias/CategoriaContext';
const AllCat = () => {
const { categorias, GetCategorias } = useContext( CategoriaContext );
useEffect(() => {
GetCategorias();
},[])
return(
<div className="container mx-auto">
<div className="card col-md-5 mx-auto">
<h4 className="card-title text-center px-0 mx-0 border-bottom">Categorias</h4>
<div className="card-body px-0">
</div>
</div>
</div>
)
};
export default AllCat;
I know that I have some errors, because this is my first example using context in react js, I wan't is create a simple crud using context and hook, I have a file data, this file is call Data, this file have an id, description, idfather.
so please, do you can help me, the better way to work with context and usestate??
In categoriastate.js you don't set the GetCategorias member of the Provider's value, so you will only access the categorias from useContext( CategoriaContext ) and the GetCategorias will be undefined (in allcat.js).

Component does not rerender when context changes

I've been playing around with the react context api and I'm just not getting why it's not working.
I have a component with a container that should show or hide depending on a valuer stored in context.
This is the component:
import React, { useContext } from 'react';
import ResultsContext from '../../context/results/resultsContext';
const ResultsPanelContainer = () => {
const resultsContext = useContext(ResultsContext);
const { showResults } = resultsContext;
console.log('showResults in ResultsPanelConatiner: ', showResults);
return (
<div
className='container-fluid panel'
style={{ display: showResults ? 'block' : 'none' }}
>
<div className='container'>
<div className='row'>
<div className='col'>
<h1 className='display-4'>Results.Panel.js</h1>
</div>
</div>
</div>
</div>
);
};
export default ResultsPanelContainer;
For completeness, the context is divided up into three sections, the call to the context itself, a 'state' file and a reducer. These are displayed below:
resultsContext.js
import { createContext } from 'react';
const resultsContext = createContext();
export default resultsContext;
ResultsState.js
import React, { useReducer } from 'react';
// import axios from 'axios';
import ResultsContext from './resultsContext';
import ResultsReducer from './resultsReducer';
import { UPDATE_SHOW_RESULTS } from '../types';
const ResultsState = (props) => {
const initialState = {
showResults: false,
};
const [state, dispatch] = useReducer(ResultsReducer, initialState);
const updateShowResults = (data) => {
console.log('updateShowResults - ', data);
dispatch({
type: UPDATE_SHOW_RESULTS,
payload: data,
});
};
return (
<ResultsContext.Provider
value={{
showResults: state.showResults,
updateShowResults,
}}
>
{props.children}
</ResultsContext.Provider>
);
};
export default ResultsState;
resultsReducer.js
import { UPDATE_SHOW_RESULTS } from '../types';
export default (state, action) => {
switch (action.type) {
case UPDATE_SHOW_RESULTS:
return {
...state,
showResults: action.payload,
};
default:
return state;
}
};
The change is triggered by a button click in a separate component and this does trigger an update in the context as shown when you log it to the console. However, the component is not rerendering.
I understand from reading various answers on here that changing context doesn't trigger a rerender of all child components in the same way that setState does. However, the component displaying this is calling the context directly so as far as I can see the rerender should take effect.
Am I missing something glaringly obvious?
Thanks in advance.
Stef
Forget the above... I'm an idiot - wrapped the two separate parts of the app in two separate instances of ResultsState which weren't communicating. Did this:
const App = () => {
return (
<Fragment>
<UsedDataState>
<Header />
</UsedDataState>
<main>
<ExportPanelContainer />
<ResultsState>
<SendQueryState>
<OrQueryState>
<AndQueryState>
<QueryPanelContainer />
</AndQueryState>
</OrQueryState>
</SendQueryState>
</ResultsState>
<ResultsState>
<ResultsPanelContainer />
</ResultsState>
</main>
</Fragment>
);
};
Instead of this:
const App = () => {
return (
<Fragment>
<UsedDataState>
<Header />
</UsedDataState>
<main>
<ExportPanelContainer />
<ResultsState>
<SendQueryState>
<OrQueryState>
<AndQueryState>
<QueryPanelContainer />
</AndQueryState>
</OrQueryState>
</SendQueryState>
<ResultsPanelContainer />
</ResultsState>
</main>
</Fragment>
);
};
Hope this is useful for someone else...

Switch to results Component `onSubmit` a search form in react hooks using react router

Hi I have a scenario where I put a search bar on the top nav so a user can search from anywhere in the app. How to do I switch to the results component once the user submits the search form? Here's my search component that populates the global state with search results but I can't manage to switch the view to the results component.
import React, { useState, useEffect, useContext } from 'react';
import axios from 'axios';
import { StateContext } from '../../StateContext';
import './SearchBar.scss';
import sprite from '../../assets/icons/sprite.svg';
function SearchBar() {
const [state, setState] = useContext(StateContext);
const [userInput, setUserInput] = useState('');
const [bookName, setBookName] = useState('');
useEffect(() => {
axios
.get(`https://www.googleapis.com/books/v1/volumes?q=${bookName}`)
.then((res) => {
let book_list = res.data.items;
setState({
book_list: book_list,
heading: 'Search Results'
});
})
.catch((err) => console.log(err));
}, [bookName]);
const findBook = (e) => {
e.preventDefault();
setBookName(userInput);
};
const onChange = (e) => {
setUserInput(e.target.value);
};
return (
<form className='searchbar' onSubmit={findBook}>
<input
type='search'
className='searchbar__input'
placeholder='Search for a book'
value={userInput}
onChange={onChange}
/>
<button className='searchbar__button'>
<svg className='searchbar__icon'>
<use xlinkHref={`${sprite}#icon-search`} />
</svg>
</button>
</form>
);
}
export default SearchBar;
Here's how I'm handling routing:
import React from 'react';
import Nav from './components/Nav/Nav';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Books from './containers/Books';
import Book from './containers/Book';
import { ContextController } from './StateContext';
function App() {
return (
<ContextController>
<Router>
<div className='app'>
<Nav />
<main>
<Switch>
<Route exact path='/' component={Books} />
<Route exact path='/book/:id' component={Book} />
</Switch>
</main>
</div>
</Router>
</ContextController>
);
}
export default App;
If you have a dedicated route for search results, try this in your ContextController
import { useHistory } from 'react-router-dom';
// later
const history = useHistory();
React.useEffect(() => {
if (state?.book_list?.length > 0) {
history.push('/search-results');
}
}, [state]);
Also, it is important to note that the Router should be on top of your Data Context;
Because if you want to access the history from the a tree, it needs to be wrapped in a Router, or else it will return undefined as a value for history
Here is a working codesandbox

Blink previous result before render new result

I am new in react and redux. I am fetching data from one page page and I would like to show details of the record. When I click from the list to see details for the first time, it is fine. But when I would like to see the next result details and for a millisecond there is a blink of previous result and then re rendered by the new one. Could you help me with this issue, please? I do not want to see the previous artist detail.
Show details
import React from 'react';
import { connect } from 'react-redux';
import { fetchArtistDetails } from '../../actions';
class ResultDetails extends React.Component{
componentDidMount(){
this.props.fetchArtistDetails(this.props.match.params.id);
}
renderList(){
return this.props.artist.map(artist => {
return(
<div className="ui segment">
<br/>
<div className="ui two column centered grid">
<div className="ui centered massive label">{artist.name}</div>
</div>
<br/>
<br/>
{ (artist.images) ?
artist.images.map(image =>{
return image.type ==='primary' ? <img className="ui centered medium image" src={image.uri}/> : null
}) : null
}
<div className="ui small images">
{ (artist.images) ?
artist.images.map(image =>{
return image.type ==='secondary' ? <img src={image.uri}/> : null
}) : null
}
</div>
<p>{artist.profile}</p>
</div>
);
});
}
render(){
if (!this.props.artist) {
console.log("ahoj");
return <div>Loading...</div>;
}
return(
<div>
<div>{this.renderList()}</div>
</div>
);
}
}
const mapStateToProps = state => {
return { artist: state.artistDetails }
}
export default connect( mapStateToProps , { fetchArtistDetails }) (ResultDetails);
Actions
import discogs from '../apis/discogs';
import history from '../history';
import { FETCH_POSTS, SEARCH_ARTIST, FETCH_ARTIST_DETAILS, CLEAN_ARTIST_DETAILS } from './types';
export const fetchPosts = text => async dispatch => {
const response = await discogs.get(`/database/search?q=${text}&type=artist`);
dispatch({ type: FETCH_POSTS, payload: response.data.results });
history.push(`/results/${text}`);
};
export const searchArtist = text => dispatch => {
dispatch({
type: SEARCH_ARTIST,
payload: text
});
};
export const fetchArtistDetails = id => async dispatch => {
const response = await discogs.get(`/artists/${id}`);
dispatch({ type: FETCH_ARTIST_DETAILS, payload: response.data });
history.push(`/details/${id}`);
};
Reducer
import {
FETCH_ARTIST_DETAILS,
} from '../actions/types';
export default (state = [], action) => {
switch (action.type) {
case FETCH_ARTIST_DETAILS:
return [action.payload]
default:
return state;
}
};
App
import React, { Fragment } from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import MainPage from '../pages/MainPage';
import SearchResult from '../components/searchResult/SearchResult';
import ResultDetails from '../components/resultDetails/ResultDetails';
import Header from './header/Header';
import history from '../history';
const App = () => {
return (
<div className="ui container">
<Router history={history}>
<div>
<Switch>
<Route exact path='/' component={MainPage} />
<Fragment>
<Header />
<Route exact path="/results/:id" component={SearchResult} />
<Route exact path="/details/:id" component={ResultDetails} />
</Fragment>
</Switch>
</div>
</Router>
</div>
);
};
export default App;
I found and issue (I do not know, if it is proper solution but it works for me)
New action
export const cleanArtistDetails = () => async dispatch => {
dispatch({ type: CLEAN_ARTIST_DETAILS, payload: null });
};
Update reducer
import {
FETCH_ARTIST_DETAILS, CLEAN_ARTIST_DETAILS,
} from '../actions/types';
export default (state = [], action) => {
switch (action.type) {
case FETCH_ARTIST_DETAILS:
return [action.payload]
case CLEAN_ARTIST_DETAILS:
return null
default:
return state;
}
};
And update component
componentWillUnmount(){
this.props.cleanArtistDetails();
}

Resources