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);
Related
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>();
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.
as mentioned in the title I'm trying to set up some test for <Search /> component, in particular I want to test the useState hooks.
After mocking the Redux store and creating a shallow wrapper I tried to simulate an input from the child component DisplaySearchBar but apparently I cannot even mamage to select it.
That's the error I get:
Method “props” is meant to be run on 1 node. 0 found instead.
Here's Search.js
import React, { useState } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { handleScriptLoad } from '../../../helpers/Autocomplete';
import { getRestaurants, setAlert } from '../../../actions/restaurantAction';
import DisplaySearchBar from '../../layout/DisplaySearchBar/DisplaySearchBar';
import styles from './Search.module.scss';
const Search = ({ getRestaurants, setAlert }) => {
const [where, setWhere] = useState('');
const [what, setWhat] = useState('');
const [sortBy, setSortBy] = useState('rating');
const sortByOptions = {
'Highest Rated': 'rating',
'Best Match': 'best_match',
'Most Reviewed': 'review_count',
};
// give active class to option selected
const getSortByClass = (sortByOption) => {
if (sortBy === sortByOption) {
return styles.active;
} else {
return '';
}
};
// set the state of a sorting option
const handleSortByChange = (sortByOption) => {
setSortBy(sortByOption);
};
//handle input changes
const handleChange = (e) => {
if (e.target.name === 'what') {
setWhat(e.target.value);
} else if (e.target.name === 'where') {
setWhere(e.target.value);
}
};
const onSubmit = (e) => {
e.preventDefault();
if (where && what) {
getRestaurants({ where, what, sortBy });
setWhere('');
setWhat('');
setSortBy('best_match');
} else {
setAlert('Please fill all the inputs');
}
};
// displays sort options
const renderSortByOptions = () => {
return Object.keys(sortByOptions).map((sortByOption) => {
let sortByOptionValue = sortByOptions[sortByOption];
return (
<li
className={getSortByClass(sortByOptionValue)}
key={sortByOptionValue}
onClick={() => handleSortByChange(sortByOptionValue)}
>
{sortByOption}
</li>
);
});
};
return (
<DisplaySearchBar
onSubmit={onSubmit}
handleChange={handleChange}
renderSortByOptions={renderSortByOptions}
where={where}
what={what}
handleScriptLoad={handleScriptLoad}
/>
);
};
Search.propTypes = {
getRestaurants: PropTypes.func.isRequired,
setAlert: PropTypes.func.isRequired,
};
export default connect(null, { getRestaurants, setAlert })(Search);
DisplaySearchBar.js
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { clearSearch } from '../../../actions/restaurantAction';
//Import React Script Libraray to load Google object
import Script from 'react-load-script';
import Fade from 'react-reveal/Fade';
import Alert from '../Alert/Alert';
import styles from './DisplaySearchBar.module.scss';
const DisplaySearchBar = ({
renderSortByOptions,
onSubmit,
where,
handleChange,
what,
handleScriptLoad,
restaurants,
clearSearch,
}) => {
const googleUrl = `https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_API_KEY}&libraries=places`;
// {googleUrl && <Script url={googleUrl} onLoad={handleScriptLoad} />}
return (
<section className={styles.searchBar}>
<form onSubmit={onSubmit} className={styles.searchBarForm}>
<legend className="title">
<Fade left>
<h1>Where are you going to eat tonight?</h1>
</Fade>
</legend>
<Fade>
<fieldset className={styles.searchBarInput}>
<input
type="text"
name="where"
placeholder="Where do you want to eat?"
value={where}
onChange={handleChange}
id="autocomplete"
/>
<input
type="text"
name="what"
placeholder="What do you want to eat?"
onChange={handleChange}
value={what}
/>
<div className={styles.alertHolder}>
<Alert />
</div>
</fieldset>
<fieldset className={styles.searchBarSubmit}>
<input
id="mainSubmit"
className={`${styles.myButton} button`}
type="submit"
name="submit"
value="Search"
></input>
{restaurants.length > 0 && (
<button
className={`${styles.clearButton} button`}
onClick={clearSearch}
>
Clear
</button>
)}
</fieldset>
</Fade>
</form>
<article className={styles.searchBarSortOptions}>
<Fade>
<ul>{renderSortByOptions()}</ul>
</Fade>
</article>
</section>
);
};
DisplaySearchBar.propTypes = {
renderSortByOptions: PropTypes.func.isRequired,
where: PropTypes.string.isRequired,
handleChange: PropTypes.func.isRequired,
what: PropTypes.string.isRequired,
handleScriptLoad: PropTypes.func.isRequired,
restaurants: PropTypes.array.isRequired,
clearSearch: PropTypes.func.isRequired,
};
const mapStatetoProps = (state) => ({
restaurants: state.restaurants.restaurants,
});
export default connect(mapStatetoProps, { clearSearch })(DisplaySearchBar);
And Search.test.js
import React from 'react';
import { mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import Search from '../Search';
import DisplaySearchBar from '../../../layout/DisplaySearchBar/DisplaySearchBar';
const mockStore = configureStore();
const initialState = {
restaurants: { restaurants: ['foo'], alert: null },
};
describe('Search', () => {
test('renders withut errors', () => {
const store = mockStore(initialState);
const wrapper = mount(
<Provider store={store}>
<Search setAlert={jest.fn()} getRestaurants={jest.fn()} />
</Provider>
);
wrapper.find(DisplaySearchBar).props();
});
});
Thanks for your help!
shallow doesn't work for react-redux new versions (>= 6).
Use mount instead:
const wrapper = mount( // <-- changed shallow to mount.
<Provider store={store}>
<Search {...props} />
</Provider>
);
Run It On Sandbox (Use tests tab to run tests.)
Try to mount it like this:
const wrapper = shallow(
<Provider store={store} />
<Search setAlert=jest.fn() getRestaurants=jest.fn() />
</Provider>
);
I've got a container/component (from Redux examples) complaining about "dispatch is not a function". I had this working before I added Recompose. I think Recompose puts a wrapper around dispatch(), so I need to expose it somehow. Maybe applyMiddleware will do the trick, but I don't know where to hook it up? What do I need to do?
Container:
const AddTodo = (props, dispatch) => {
let input;
const { classes } = props;
return (
<div>
<form
id="my-form-id"
onSubmit={e => {
e.preventDefault();
if (!input.value.trim()) {
return;
}
dispatch(addTodo(input.value));//<<<OFFENDING LINE
input.value = "";
}}
>
<TextField
id="agentName"
label="Agent Name"
placeholder="Placeholder"
form="my-form-id"
inputRef={el => (input = el)}
className={classes.textField}
margin="normal"
/>
<Button variant="extendedFab" type="submit" className={classes.button}>
<AddIcon className={classes.addIcon} />
New Todo
</Button>
</form>
</div>
);
};
export default compose(
withStyles(styles),
connect()
)(AddTodo);
Root index.js:
import React from "react";
import { render } from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import App from "./components/App";
import rootReducer from "./reducers";
const store = createStore(rootReducer);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
There are two basic things to understand.
1.
When composing connect() Redux adds dispatch as a prop.
export default compose(
withStyles(styles),
connect() // <-- This adds dispatch to props.
)(AddTodo);
2.
You should access props as a single object or destructure branches of the props object.
This line is where the misunderstanding is happening.
const AddTodo = (props, dispatch) => { // <-- dispatch is not an parameter, it exists at props.dispatch
To fix things using your existing pattern do this.
const AddTodo = (props) => {
let input;
const { classes, dispatch } = props;
return (
...
Optionally you can destructure the props parameter directly.
const AddTodo = ({ classes, dispatch }) => {
let input;
return (
...
With either approach the remaining code will work as expected.
connect passes the dispatch as a prop to the connected component, you should destructure it from the props.
const AddTodo = ({classes, dispatch}) => {
let input;
return (
<div>
<form
id="my-form-id"
onSubmit={e => {
e.preventDefault();
if (!input.value.trim()) {
return;
}
dispatch(addTodo(input.value));
input.value = "";
}}
>
<TextField
id="agentName"
label="Agent Name"
placeholder="Placeholder"
form="my-form-id"
inputRef={el => (input = el)}
className={classes.textField}
margin="normal"
/>
<Button variant="extendedFab" type="submit" className={classes.button}>
<AddIcon className={classes.addIcon} />
New Todo
</Button>
</form>
</div>
);
};
export default compose(
withStyles(styles),
connect()
)(AddTodo);
i don't know what im doing wrong.
I thought i got the point how to implement with redux.
My Problem is that the state of the Component is not propagating to the Component after the state changed. I know we have to create new state object and i'm sure im doing it right. But there is no changes.
And the other question is, why the state is in the object "resetLink" see image. Why is it not stored in the state Object?
Please help me to get it work.
Action for the redux:
export const sendResetLink = (email) => {
return {
type: RESET_LINK,
email
}
}
class App extends React.Component {
render() {
return <VisibleResetLink/>
}
}
This is where the magic with props and dispatch function happens:
import {connect} from 'react-redux';
import SendResetLink from '../components/SendResetLink.jsx';
import {sendResetLink} from '../actions/index'
import _ from 'lodash';
const mapDispatchToProps = (dispatch) => {
return {
onClick: (email) => {
dispatch(sendResetLink(email))
}
}
}
const mapStateToProps = (state) => {
console.log("actual state", state);
return _.assign({} , {state: state.resetLink});
}
const VisibleResetLink = connect(
mapStateToProps,
mapDispatchToProps
)(SendResetLink)
export default VisibleResetLink
This is the Component which load props but never rerender them when they change.
import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';
class SendResetLink extends React.Component {
constructor(props, email, result) {
super(props);
console.log('props',props);
this.onClick = props.onClick;
this.result = props.state.result;
this.input = props.state.email;
}
renderResult (result){
console.log("renderResult",result);
if(result)
return <div className="card-panel deep-orange lighten-4">Invalid username and password.</div>
}
render() {
return <div>
{this.renderResult(this.result)}
{this.result}
<form className="col s12" action="/login" method="post" onSubmit={e => {
e.preventDefault()
if (!this.input.value.trim()) {
return
}
this.onClick(this.input.value);
this.input.value = ''
} }>
<div className="row">
<div className="input-field col s12">
<i className="material-icons prefix">email</i>
<input id="email" type="email" name="email" className="validate" ref = {(node) => this.input = node } />
<label for="email">Email</label>
</div>
</div>
<div className="divider"></div>
<div className="row">
<div className="col m12">
<p className="right-align">
<button className="btn btn-large waves-effect waves-light" type="submit" name="action">Send reset key</button>
</p>
</div>
</div>
</form></div>
}
}
SendResetLink.propTypes = {
onClick: PropTypes.func.isRequired,
state: PropTypes.object.isRequired
}
export default SendResetLink
And this is the other relevant code snippet, where the reducer is implemented.
import _ from 'lodash';
const resetLink = (state = {email:"test#test.de", result:true}, action) => {
console.log('state', state)
switch (action.type) {
case 'RESET_LINK':
return _.assign({},state,{email: action.email, result: false});
default:
return state
}
}
export default resetLink;
import { combineReducers } from 'redux'
import resetLink from './ResetLinkReducer.jsx'
const resetApp = combineReducers({
resetLink
})
export default resetApp
import App from './components/app.jsx';
import {Provider} from 'react-redux';
import { createStore } from 'redux';
import { render } from 'react-dom';
import React from 'react';
import resetApp from './reducers/index.js';
let store = createStore(resetApp,{});
console.log(store)
render(<Provider store={store}>
<App />
</Provider>, document.getElementById('sendResetLinkComponent'));
console logs. After click the renderResult should change to false. But it stays on true
You set this.result in your SendResetLink component only once in the constructor, which is only executed when the component is instantiated. The constructor will not be executed every time the application's state changes:
this.result = props.state.result;
Instead of assigning this part of the state to an instance attribute of your react component, simply use the props attribute directly in your render() function:
{this.renderResult(this.props.state.result)}