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.
Related
I have my homepage where I received the products with the redux method but I did not want to render them on the home page so I did it with a single product component, but again I wanted to display the products in react-Alice-carousel I sent the products in the homepage through props and destrusctured it in the Single product and tried to create the items props of react-alice-carousel through jsx but got an error product.map is not a function.
My Single Product Component.
import React from "react";
import AliceCarousel from "react-alice-carousel";
// import { Button, Card, Container } from "react-bootstrap";
// import { FaShoppingCart } from "react-icons/fa";
// import { useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
// import { AddToCartAction, RemoveFromCart } from "../../actions/cartActions";
import UICARD from "../../interface/UICARD";
// import classes from "./home.module.css";
export function numberWithComas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
const SingleProduct = ({ product }) => {
const items = product.map((p) => {
return (
<Link to={`/product/${p._id}`}>
<UICARD>
<img src={p.image} alt={p.name} />
<span>
{p.name}
<span>{p.description}</span>
</span>
<span>{p.price}</span>
</UICARD>
</Link>
);
});
const responsive = {
0: {
items: 4,
},
512: {
items: 6,
},
};
return (
<div>
<AliceCarousel
items={items}
disableDotsControls
infinite
mouseTracking
responsive={responsive}
/>
</div>
);
};
export default SingleProduct;
My Home Page
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Products } from "../../actions/productsActions";
import ErrorMessage from "../../helpers/ErrorMessage";
import Loading from "../../helpers/Loading";
import SideBar from "../Home/SideBar";
import SingleProduct from "../Home/SingleProduct";
import classes from '../Home/home.module.css'
const Home = () => {
const dispatch = useDispatch();
const productList = useSelector((state) => state.productList);
const { products, error, loading } = productList
console.log(products);
useEffect(() => {
dispatch(Products());
},[dispatch])
return (
<div className ={classes.home}>
{error && <ErrorMessage variant={error.info?"info":"danger"}>{error}</ErrorMessage>}
{loading && <Loading />}
<SideBar />
<div className={classes.productContainer}>
{
products.map((product) => {
return <SingleProduct product={product} key={product._id} />
})
}
</div>
</div>
);
};
export default Home;
You need to change you home compoent like that since singleProduct need to have a props peroduct as an array , i recommand to use prop-types to avoid this kind of problems
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Products } from "../../actions/productsActions";
import ErrorMessage from "../../helpers/ErrorMessage";
import Loading from "../../helpers/Loading";
import SideBar from "../Home/SideBar";
import SingleProduct from "../Home/SingleProduct";
import classes from '../Home/home.module.css'
const Home = () => {
const dispatch = useDispatch();
const productList = useSelector((state) => state.productList);
const { products, error, loading } = productList
console.log(products);
useEffect(() => {
dispatch(Products());
},[dispatch])
return (
<div className ={classes.home}>
{error && <ErrorMessage variant={error.info?"info":"danger"}>{error}</ErrorMessage>}
{loading && <Loading />}
<SideBar />
<div className={classes.productContainer}>
<SingleProduct product={products} />
</div>
</div>
);
};
export default Home;
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).
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
I'm trying to add apollo to a walmart electrode starter react app and I'm getting the warning about the client property.
It seems to work fine if I just use the apollo provider to wrap a component that is not a react-router. When I add the react router- it shows that warning - however the page still loads with apollo fine.
Warning: Failed context type: The context `client` is marked as required in `Apollo(Home)`, but its value is `undefined`.
in Apollo(Home) (created by Connect(Apollo(Home)))
in Connect(Apollo(Home)) (created by RouterContext)
in RouterContext
in Provider
app.jsx
//
// This is the client side entry point for the React app.
//
import React from "react";
import {render} from "react-dom";
import {routes} from "./routes";
import {Router} from "react-router";
import {createStore} from "redux";
import "./styles/base.css";
import rootReducer from "./reducers";
import ApolloClient, {createNetworkInterface} from "apollo-client";
import { ApolloProvider } from "react-apollo";
//
// Add the client app start up code to a function as window.webappStart.
// The webapp's full HTML will check and call it once the js-content
// DOM is created.
//
const graphUri = "https://api.graph.cool/simple/v1/foo";
window.webappStart = () => {
const initialState = window.__PRELOADED_STATE__;
const store = createStore(rootReducer, initialState);
const client = new ApolloClient({
networkInterface: createNetworkInterface({ uri: graphUri })
});
render(
<ApolloProvider store={store} client={client}>
<Router>{routes}</Router>
</ApolloProvider>,
document.querySelector(".js-content")
);
};
home.jsx
import React, {PropTypes} from "react";
import {connect} from "react-redux";
import {toggleCheck, incNumber, decNumber} from "../actions";
import gql from "graphql-tag";
import { graphql } from "react-apollo";
class Home extends React.Component {
render() {
const props = this.props;
const {checked, value} = props;
const { loading, allExperiences } = this.props.data;
if (loading) {
return <div>Loading</div>;
} else {
return (
<div>
<div>
<h2>Managing States with Redux</h2>
<label>
<input onChange={props.onChangeCheck} type={"checkbox"} checked={checked}/>
Checkbox
</label>
<div>
<button type={"button"} onClick={props.onDecrease}>-</button>
{value}
<button type={"button"} onClick={props.onIncrease}>+</button>
</div>
</div>
<ul>
{allExperiences.map(post =>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
</div>
);
}
}
}
Home.propTypes = {
checked: PropTypes.bool,
data: PropTypes.shape({
loading: PropTypes.bool.isRequired,
allExperiences: PropTypes.array
}).isRequired,
value: PropTypes.number.isRequired
};
const mapStateToProps = (state) => {
return {
checked: state.checkBox.checked, value: state.number.value
};
};
const mapDispatchToProps = (dispatch) => {
return {
onChangeCheck: () => {
dispatch(toggleCheck());
},
onIncrease: () => {
dispatch(incNumber());
},
onDecrease: () => {
dispatch(decNumber());
}
};
};
const getQuery = gql`
query allExperiences {
allExperiences {
id,
title,
subtitle,
description
}
}`;
const HomeWithData = graphql(getQuery)(Home);
export default connect(mapStateToProps, mapDispatchToProps)(HomeWithData);
routes.jsx
import React from "react";
import {Route} from "react-router";
import Home from "./components/home";
export const routes = (
<Route path="/" component={Home}/>
);
I am not sure if I am even setting up this redux-react project correctly. I am confused as to how I can actually start using store within my react app.
When I try to console.log store I am getting undefined. I have gotten most of this from a boilerplate and am unsure of how some of these parts interact. Currently I have an index.js with
import { Provider } from 'react-redux'
import { configureStore } from './store/configureStore';
const store = configureStore()
import { Root} from './containers/Root';
import Home from './containers/Home'
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={Root}>
<IndexRoute component={Home} />
</Route>
</Router>
</Provider>,
document.getElementById('root')
);
Root.js :
import React, { Component } from 'react';
import DevTools from './DevTools';
import MyNavbar from '../components/MyNavbar';
import Footer from '../components/Footer'
module.exports = class Root extends Component {
render() {
const { store } = this.props;
console.log(store)
return (
<div>
<MyNavbar />
{this.props.children}
<Footer />
{/* Being the dev version of our Root component, we include DevTools below */}
{/*<DevTools />*/}
</div>
);
}
};
Home component:
import React, { Component, PropTypes } from 'react';
import { Row, Col, Grid } from 'react-bootstrap'
import HowItWorks from '../components/HowItWorks'
import GetStarted from '../components/GetStarted'
import Setup from './Setup'
export default class Home extends Component {
render() {
// we can use ES6's object destructuring to effectively 'unpack' our props
return (
<section>
<div className="slider-wrapper">
<GetStarted />
</div>
<Grid>
<div className="howwork-wrapper">
<Row >
<Col md={12}>
<HowItWorks />
</Col>
</Row>
</div>
</Grid>
</section>
);
}
}
configureStore.js :
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from '../reducers';
import createLogger from 'redux-logger';
import thunk from 'redux-thunk';
import DevTools from '../containers/DevTools';
const logger = createLogger();
const finalCreateStore = compose(
applyMiddleware(logger, thunk),
DevTools.instrument()
)(createStore);
module.exports = function configureStore(initialState) {
const store = finalCreateStore(rootReducer, initialState);
if (module.hot) {
module.hot.accept('../reducers', () =>
store.replaceReducer(require('../reducers'))
);
}
return store;
};
reducers/index.js:
import { combineReducers } from 'redux';
import auth from './auth'
const rootReducer = combineReducers({
auth
});
export default rootReducer;
reducers/auth.js:
import { LOGIN, LOGIN_FAIL, LOGOUT } from '../constants/ActionTypes'
export default function auth(state = {}, action) {
switch (action.type) {
case LOGIN:
return state;
case LOGIN_FAIL:
return state ;
case LOGOUT:
return state ;
default:
return state;
}
}
constants/ActionTypes:
export const LOGIN = 'LOGIN';
export const LOGIN_FAIL = 'LOGIN_FAIL';
export const LOGOUT = 'LOGOUT';
You need to connect your components to get access to the store/state. To do this, modify your Root component like this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import DevTools from './DevTools';
import MyNavbar from '../components/MyNavbar';
import Footer from '../components/Footer'
class Root extends Component {
render() {
const { state } = this.props;
console.log(state)
return (
<div>
<MyNavbar />
{this.props.children}
<Footer />
{/* Being the dev version of our Root component, we include DevTools below */}
{/*<DevTools />*/}
</div>
);
}
};
const mapStateToProps = (state) => {
return {
state: state
}
}
module.exports = connect(mapStateToProps)(Root);
A few notes, since you are transpiling anyway, you could export instead of module.exports in your declaration. Also, generally you do not want to expose your entire state to a single component. You can connect multiple components (make them "containers") by following this pattern.
The following is an example component connected to your state.
import React, { Component } from 'react';
import { connect } from 'react-redux';
export class SomeComponent extends Component {
render() {
const { someKey, dispatchSomething } = this.props;
return (
<div onClick={dispatchSomething}>
<h1>My rendered someKey variable: {someKey}</h1>
</div>
);
}
};
const mapStateToProps = (state) => {
return {
someKey: state.someReducer.someKey
}
}
const mapDispatchToProps = (dispatch) => {
return {
dispatchSomething: () => dispatch(someActionCreator())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SomeComponent);
References
react-redux API: connect