I am receiving this error on CodeSandbox: Cannot read properties of undefined (reading 'getState')
What is the proper way to use createSelectorHook()? Could someone please create a CodeSandbox to illustrate its usage?
index.js:
import React from "react";
import { createRoot } from "react-dom/client";
import { createStore } from "redux";
import reducer from "./reducer";
import App from "./App";
import myContext from "./context";
import {
Provider,
createStoreHook,
createDispatchHook,
createSelectorHook
} from "react-redux";
export const useStore = createStoreHook(myContext);
export const useDispatch = createDispatchHook(myContext);
export const useSelector = createSelectorHook(myContext);
const store = createStore(reducer, 0);
const root = createRoot(document.getElementById("root"));
root.render(
<Provider store={store} context={myContext}>
<App />
</Provider>
);
App.js:
import React, { useState, useRef } from "react";
import { useSelector, useDispatch } from "./";
import context from "./context";
const Counter = (props) => {
return (
<context.Provider value={props}>
<A />
</context.Provider>
);
};
const A = (props) => {
return <B />;
};
const B = (props) => {
return <C />;
};
const C = (props) => {
const v = useSelector((state) => state);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
value:<span>{v}</span>
<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
</div>
);
};
const App = () => {
const stepInput = useRef(1);
const [step, updateStep] = useState(1);
return (
<div>
step:
<input
ref={stepInput}
type="number"
onChange={() => updateStep(stepInput.current.value)}
/>
<Counter step={step} />
</div>
);
};
export default App;
I think react-redux v8.0.0 removed DefaultRootState (inferring the state):
Now that React-Redux itself is written in TS, we've opted to remove the DefaultRootState type entirely. State generics now default to unknown instead.
source: the release notes
So, createStoreHook appears to exist if you'd like to (a) bind to a custom context or, (b) ensure a call to something like useState().getStore() returns a properly typed redux store.
Note, I am not positive, if this is a correct or even suggested use case for (b). I also discovered that this works following the documented example for useAppDispatch:
export const useAppStore = () => useStore<RootState>();
Related
Can someone tell me if there is anything wrong with the way I have used the Context API in this code. And if there is something wrong can you explain why?
These two are my Contexts
import React from "react";
export const ItemListContext = React.createContext();
import React from "react";
export const ItemContext = React.createContext();
This is my App component
import "./styles.css";
import TodoList from "./Components/TodoList";
import { ItemContext } from "./Context/ItemContext";
import { ItemListContext } from "./Context/ItemListContext";
import { useState } from "react";
export default function App() {
const [inputs, setInput] = useState("");
const [itemList, setItemList] = useState([]);
return (
<div className="App">
<ItemContext.Provider value={[inputs, setInput]}>
<ItemListContext.Provider value={[itemList, setItemList]}>
<TodoList />
</ItemListContext.Provider>
</ItemContext.Provider>
</div>
);
}
After this I have a Todo List component that looks like this :-
import React, { useContext, useState } from "react";
import { ItemContext } from "../Context/ItemContext";
import { ItemListContext } from "../Context/ItemListContext";
const TodoList = () => {
const [input, setInput] = useContext(ItemContext);
const [itemList, setItemList] = useContext(ItemListContext);
const handleChange = (e) => {
setInput(e.target.value);
};
const hanleCLick = () => {
setItemList((prevList) => [...prevList, input]);
};
const handleDelete = (i) => {
let newList = itemList.filter((item, index) => index !== i);
setItemList(newList);
};
return (
<>
<input type="text" value={input} onChange={handleChange} />
<button onClick={hanleCLick}>Add Item</button>
{itemList.map((item, index) => {
return (
<div key={index}>
<p>{item}</p>
<button onClick={() => handleDelete(index)}>Delete</button>
</div>
);
})}
</>
);
};
export default TodoList;
I am trying to connect the metamask wallet to my react-app. But in the process I am getting a few errors as mentioned below please help.
getWeb3.js
import Web3 from 'web3';
import {useEffect, useState} from 'react';
// Wallet connect code
const useWeb3 = () => {
const [web3,setweb3] = useState(null);
useEffect(() => {
var instance;
if(window.ethereum) {
try{
instance = new Web3(window.ethereum);
}catch(error){
console.error(error);
};
}else if (window.web3) {
instance = new Web3(window.web3);
}else{
const provider = new Web3.provider.HttpProvider('http://127.0.0.1:8545')
instance = new Web3(provider);
}setweb3(instance);
},[]);
return web3;
};
export {useWeb3};
store.js
import React, {useReducer, useContext, createContext} from 'react';
const StoreContext = createContext();
const initialState = {
message: '',
address: null,
balance: 0
};
const reducer = (state, action) => {
switch(action.type){
case "NEW-ADDRESS":
return {
...state,
address: action.newAddress,
message: action.message
}
default:
throw new Error('Unknown type of action ${action.type');
}
};
export const StoreProvider = ({children}) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StoreContext.Provider value = {{state, dispatch}}>
{children}
</StoreContext.Provider>
);
};
export const useStore = () => useContext(StoreContext);
storeApi.js
import {useStore} from './store'
const useStoreApi = () => {
const {state, dispatch} = useStore();
return {
address: state.address,
balance: state.balance,
message: state.message,
setBalance: (newBalance) => {
dispatch({
type: "SET-BALANCE",
newBalance
});
},
setAddress: newAddress => {
dispatch({
type: "New-Address",
newAddress,
message: "New account added successfully!"
});
}
};
};
export {useStoreApi};
app.js
import { useStoreApi } from './storeApi';
import { useWeb3 } from './getWeb3';
import logo from './logo.svg';
import './App.css';
import {Button} from '#material-ui/core';
function App() {
const {address,balance,message, setBalance,setAddress} = useStoreApi();
const web3 = useWeb3();
const setUserAccount = async (e) => {
console.log("rhis add");
if(window.ethereum) {
await window.ethereum.enable();
web3.eth.getAccounts().then(accounts => {
setAddress(accounts[0]);
console.log("rhis add");
});
}
};
setUserAccount();
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
<code>src/App.js</code>
</p>
{
address ? (
<>
<p> Your add: {address}</p></>
): "s"
}
{/* <Button variant="outlined" color="primary" onClick={() => setUserAccount()}>Conn</Button> */}
{/* <form action="http://localhost:3000/">
<button type="submit" onClick={() => setUserAccount()}>Submit</button>
</form> */}
</header>
</div>
);
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {StoreProvider} from './store';
ReactDOM.render(
<React.StrictMode>
<StoreProvider>
<App />
</StoreProvider>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
Errors:
App.js:16 Uncaught (in promise) TypeError: Cannot read properties of null (reading 'eth')
at setUserAccount (App.js:16)
store.js:20 Uncaught Error: Unknown type of action ${action.type
index.js:1 The above error occurred in the component:
at StoreProvider (http://localhost:3000/static/js/main.chunk.js:847:3)
It looks like you're importing useWeb3 wrong:
const web3 = useWeb3();
But you export it like:
export {useWeb3};
So now you would have to call it like: web3.useWeb3()
Is this code you found at a tutorial somewhere? It's hard to follow...
I'm building a practice app that uses Unsplash to render users photos. I'm using React and Redux. With react-router-dom, I'm trying to follow the docs but I find it very confusing to set up. Here's what I have so far. When I click on a result out of a returned list of results from a search, I want it to render a user page profile.
index.js (make sure I have react-router-do set up correctly):
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
// import store from './app/store';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import reducers from "./app/reducers/rootReducer";
import * as serviceWorker from './serviceWorker';
const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, storeEnhancers(applyMiddleware(thunk)));
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
Top component App
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import Images from "./app/components/Images";
import Search from "./app/components/Search";
import UserProfile from "./app/components/UserProfile";
import "./App.css";
function App() {
return (
<>
<Search />
<Images />
<Router>
<Route link="/userProfile">
<UserProfile />
</Route>
</Router>
</>
);
}
export default App;
search (parent component to searchResults where exists):
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { queryAction } from "../actions/queryAction";
import SearchResults from "./SearchResults";
const Search = (props) => {
const [query, setQuery] = useState("");
console.log(props.searches);
const searchPhotos = async (e) => {
e.preventDefault();
console.log("submitting form");
props.queryAction(query);
};
const showUsers = (user, e) => {
e.preventDefault()
console.log(user)
};
return (
<>
<form className="form" onSubmit={searchPhotos}>
<label className="label" htmlFor="query">
{" "}
</label>
<input
type="text"
name="query"
className="input"
placeholder={`Try "dog" or "apple"`}
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<button type="submit" className="button">
Search
</button>
</form>
<SearchResults results={props.searches} showUsers={showUsers} />
</>
);
};
const mapStateToProps = (state) => {
return {
searches: state.searches,
};
};
const mapDispatchToProps = (dispatch) => {
return {
queryAction: (entry) => dispatch(queryAction(entry)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Search);
searchResults:
import React from "react";
import { BrowserRouter as Router, Link } from "react-router-dom";
import { getUserAction } from "../actions/getUserAction";
import { connect } from "react-redux";
const SearchResults = (props) => {
const { results } = props.results.searches;
const handleClick = (result, e) => {
e.preventDefault();
props.getUser(result.username);
};
return (
<>
{results &&
results.map((result, id) => {
return (
<div key={id}>
<Router>
<Link to="/userProfile" onClick={(e) => handleClick(result, e)}>
{result.username}
</Link>
</Router>
</div>
);
})}
</>
);
};
const mapDispatchToProps = (dispatch) => {
return {
getUser: (query) => dispatch(getUserAction(query)),
};
};
export default connect(null, mapDispatchToProps)(SearchResults);
and finally the UserProfile component:
import React from 'react';
import { connect } from 'react-redux';
const UserProfile = props => {
console.log(props)
return (
<div>
</div>
);
}
const mapStateToProps = state => {
return {
user: state.users
}
}
export default connect(mapStateToProps, null)(UserProfile);
app component
import React from "react";
import { Switch, Route } from "react-router-dom";
import Images from "./app/components/Images";
import Search from "./app/components/Search";
import UserProfile from "./app/components/UserProfile";
import "./App.css";
function App() {
return (
<>
<Search />
<Images />
<Switch>
<Route path="/userProfile/:username">
<UserProfile />
</Route>
</Switch>
</>
);
}
export default App;
SearchResults component
import React from "react";
import { Link } from "react-router-dom";
const SearchResults = (props) => {
const { results } = props.results.searches;
const handleClick = (result, e) => {
e.preventDefault();
props.getUser(result.username);
};
return (
<>
{results &&
results.map((result, id) => {
return (
<div key={id}>
<Link to={`/userProfile/${result.username}`}>
{result.username}
</Link>
</div>
);
})}
</>
);
};
export default SearchResults;
UserProfile component
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { getUserAction } from "../actions/getUserAction";
const UserProfile = props => {
useEffect(() => {
props.getUserAction(props.match.params.username)
},[])
console.log(props)
return (
<div>
{props.user
? <div>{user.username}</div>
: <div>Loading...</div>
}
</div>
);
}
const mapStateToProps = state => {
return {
user: state.users
}
}
const mapDispatchToProps = (dispatch) => {
return {
getUser: (query) => dispatch(getUserAction(query)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
Edit: Add a param to your link and remove the onclick. Update the Route to expect a :username param. You can access the param through props in UserProfile component.
Make sure to perform the action or access state when mounting the UserProfile component so you have some data when it renders.
Edit 2: Added UserProfile component to answer. You want to dispatch your action when the component is mounting. Also, set a ternary to show "Loading..." if state.user isn't done being fetched.
i'm trying to only render the component <IntercomClient /> after a user clicks "Accept" on a cookie consent banner. Clicking accept changes the GlobalLayout's intercomIsActive state to true and thereby renders the IntercomClient. This is working but the warning concerns me.
How can I workaround the child/parent state change? I've been looking around but don't really understand.
import React, { useState } from 'react'
import { CookieBanner } from '#palmabit/react-cookie-law'
import IntercomClient from '../components/intercomClient'
const GlobalLayout = ({ location, children }) => {
const [intercomIsActive, setIntercomIsActive] = useState(false)
return (
...
<CookieBanner
onAccept={() => setIntercomIsActive(true)}
/>
<IntercomClient active={intercomIsActive}/>
...
)}
IntercomClient
import React from 'react';
import Intercom from 'react-intercom'
const IntercomClient = ({ active }) => {
return active ? <div><Intercom appID="XXXXXX" /></div> : null
}
export default IntercomClient;
import React, {useState} from 'react';
const Example = () => {
const [intercomIsActive, setIntercomIsActive] = useState(false)
return (
<Layout>
...
<CookieBanner
onAccept={() => setIntercomIsActive(true)}
/>
<IntercomClient active={intercomIsActive}/>
...
</Layout>
);
};
export default Example;
import React, {useState} from 'react';
const Example = () => {
const [intercomIsActive, setIntercomIsActive] = useState(false)
return (
<Layout>
...
<CookieBanner
onAccept={() => setIntercomIsActive(true)}
/>
{
intercomIsActive &&
<IntercomClient active={intercomIsActive}/>
}
...
</Layout>
);
};
export default Example;
Ive created a Rexux store. In my entry point I can add an item to my store and see that it works:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import allReducers from './reducers';
import { ADD_TODO_ITEM } from './actionCreators';
import App from './components/containers/App';
let store = createStore(allReducers);
store.subscribe(() => console.log(store.getState()));
store.dispatch(ADD_TODO_ITEM('test 1'));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
Im trying to use this action dispatcher in my component. When I submit the form below I get the error:
TypeError: dispatch is not a function
I think Im not passing dispatch to AddTodo, but how do you pass dispatch to component?
import React from 'react';
import { ADD_TODO_ITEM } from '../../actionCreators';
const AddTodo = ({ dispatch }) => {
let input;
return (
<form
onSubmit={e => {
e.preventDefault();
const text = input.value;
console.log(text);
dispatch(ADD_TODO_ITEM(text));
}}
>
<input
type="text"
ref={node => {
input = node;
}}
/>
<button type="submit">Add Item</button>
</form>
);
};
export default AddTodo;
In your case, this.props is empty because you haven't passed any props or connected your component to your redux state. In order to have dispatch in your component, you'll need to use connect from react-redux which takes 2 arguments, one being mapStateToProps and other is mapDispatchToProps. The code goes something like this:
import React from 'react';
import {connect} from 'react-redux';
import { ADD_TODO_ITEM } from '../../actionCreators';
const AddTodo = ({ addItem }) => {
let input;
return (
<form
onSubmit={e => {
e.preventDefault();
const text = input.value;
console.log(text);
dispatch(addItem(text));
}}
>
<input
type="text"
ref={node => {
input = node;
}}
/>
<button type="submit">Add Item</button>
</form>
);
};
const mapDispatchToProps = (dispatch) => {
return {
addItem: (item) => {
dispatch(ADD_TODO_ITEM(item));
}
}
};
export default connect(undefined, mapDispatchToProps)(AddTodo);