I have several pages in my React app. Every thime when a user clicks on a page3 I want to send a fetch request to get the data and set it to the store. But for some reason Redux Toolkit dispatches twice on app load and page3 load as well. So everytime two fetch requests are made when a page loads or user switches between page3 and other pages.
How to fix that?
App.tsx
import { NavLink, Route, Routes } from "react-router-dom";
import "./styles.css";
import Todo from "./Todo";
const App = () => {
const routes = [
{
path: "/",
element: <div className={"page1"}>Page 1</div>
},
{
path: "/page2",
element: <div className={"page2"}>Page 2</div>
},
{
path: "/page3",
element: <Todo />
}
];
return (
<div className="App">
<div className="Navigation">
<NavLink to={"/"}>Page 1</NavLink>
<NavLink to={"/page2"}>Page 2</NavLink>
<NavLink to={"/page3"}>This sends a request</NavLink>
</div>
<div className="Content">
<h2>Content</h2>
<Routes>
{routes.map((route: any) => (
<Route path={route.path} element={route.element} key={route.path} />
))}
</Routes>
</div>
</div>
);
};
export default App;
Todo component
import { useEffect } from "react";
import { useDispatch } from "react-redux";
import { AppDispatch } from "./store/store";
import { fetchTodo } from "./store/TodoSlice";
const Todo: React.FC = () => {
const dispatch = useDispatch<AppDispatch>();
useEffect(() => {
dispatch(fetchTodo());
}, []);
return <div className={"page3"}>This dispatches twice</div>;
};
export default Todo;
Store slice
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
export interface ICategoryState {
todo: any[];
isLoading: boolean;
}
const initialState: ICategoryState = {
todo: [],
isLoading: false
};
const BASE_URL = "https://jsonplaceholder.typicode.com/todos";
export const fetchTodo = createAsyncThunk("todos/fetchTodo", async () => {
const response = await fetch(BASE_URL);
const data = await response.json();
return data as any[];
});
export const todoSlice = createSlice({
name: "todos",
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchTodo.pending, (state, action) => {
state.isLoading = true;
console.log("Start loading");
});
builder.addCase(fetchTodo.fulfilled, (state, { payload }) => {
state.todo = payload;
state.isLoading = false;
});
builder.addCase(fetchTodo.rejected, (state, { payload }) => {
console.log("Error happened");
state.isLoading = false;
});
}
});
export default todoSlice.reducer;
Here's a codesandbox as well: https://codesandbox.io/s/ecstatic-cookies-xicjxp?file=/src/App.tsx
By default CRA creates apps running in strict mode which causes useEffects with empty dependencies array to run twice on load.
It is supposed to help people writing better react code.
This only happens in development mode of course.
Just remove the tag
<StrictMode>
in index.tsx and it will work as desired.
I've corrected your sandbox.
Related
I'm messing around practicing concepts using react, react-router6, & redux-toolkit. Here is a quick YT short showing the problem: https://www.youtube.com/shorts/jwidQfibVEo
Here is the Post.js file that contains the logic for getting a post
import React, { useEffect } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import jwtDecode from 'jwt-decode';
import { useSelector, useDispatch } from 'react-redux';
import { getPostAsync, postsSelector, deletePostAsync } from '../redux/slices/postSlice';
const Post = () => {
const { post } = useSelector(postsSelector);
const { postId } = useParams();
const navigate = useNavigate();
const dispatch = useDispatch();
useEffect(() => {
dispatch(getPostAsync(postId));
**where the console.logs in the video come from**
console.log(post);
}, [dispatch, postId]);
const handleDelete = () => {
dispatch(deletePostAsync(postId));
navigate('/posts')
}
const handleUpdate = () => {
navigate(`/posts/${postId}/update-post`)
}
//decode the token to get the username property off it to assign as a post's author
const token = localStorage.getItem('user');
const decode = jwtDecode(token);
return (
<div>
<h1>{post.title}</h1>
<h2>{post.body}</h2>
<p>Author: {post.author}</p>
{decode._id === post.user_id &&
<div className='button-group'>
<button onClick={handleDelete}>Delete</button>
<button onClick={handleUpdate}>Update</button>
</div>}
</div>
)
}
export default Post
Here is the initial state portion of the redux file
export const postSlice = createSlice({
name: 'posts',
initialState: {
posts: [],
post: {},
loading: false,
error: false
}
Here is the logic in redux setting the payload to the state for a post
getPost: (state, action) => {
state.post = action.payload;
}
Here is the logic for making a GET request for a api/posts/:postId route that sets the data as the payload for the state using getPost()
export const getPostAsync = (postId) => async (dispatch) => {
try {
const response = await axiosWithAuth().get(`api/posts/${postId}`);
dispatch(getPost(response.data));
} catch (error) {
dispatch(setError(error));
}
}
The main issue was still that when I was on say Post 1 & then go back
to posts & then click the link for Post 3, it will show Post 1's data
for a quick millisecond before showing the data for Post 3.
This is because the current post data is still in the redux store until you fetch a new post and replace it. You can clear out the post state when the Post component unmounts when navigating back to the main "/posts" page.
Example:
useEffect(() => {
dispatch(getPostAsync(postId));
return () => {
dispatch(getPost(null)); // <-- wipe out the post state when unmounting
};
}, [dispatch, postId]);
To help with momentarily rendering blank content you can conditionally render a loading indicator while the data is fetched.
if (!post) {
return <h1>Loading Post...</h1>;
}
return (
<div>
<h1>{post.title}</h1>
<h2>{post.body}</h2>
<p>Author: {post.author}</p>
{decode._id === post.user_id && (
<div className="button-group">
<button onClick={handleDelete}>Delete</button>
<button onClick={handleUpdate}>Update</button>
</div>
)}
</div>
);
To address the navbar and the login/logout link you'll want to store the token in the redux store (persisted to/initialized from localStorage).
auth.slice.js
import { createSlice } from "#reduxjs/toolkit";
const authSlice = createSlice({
name: "auth",
initialState: {
token: JSON.parse(localStorage.getItem("user"))
},
reducers: {
login: (state, action) => {
state.token = action.payload;
localStorage.setItem("user", JSON.stringify(action.payload));
},
logout: (state) => {
state.token = null;
localStorage.removeItem("user");
}
}
});
export const actions = {
...authSlice.actions
};
export default authSlice.reducer;
store
import { configureStore } from "#reduxjs/toolkit";
import postSlice from "./slices/postSlice";
import authSlice from './slices/auth.slice';
export const store = configureStore({
reducer: {
auth: authSlice,
posts: postSlice
}
});
Navbar
const NavBar = () => {
const token = useSelector(state => state.auth.token);
const dispatch = useDispatch();
return (
<div>
<NavLink to="/" className="navlinks">
Home
</NavLink>
<NavLink to="/posts" className="navlinks">
Posts
</NavLink>
{token && (
<div>
<NavLink
to="/"
className="navlinks"
onClick={() => {
dispatch(resetPostsAsync());
dispatch(resetPostAsync());
dispatch(authActions.logout());
}}
>
Logout
</NavLink>
</div>
)}
{!token && (
<div>
<NavLink to="/register" className="navlinks">
Register
</NavLink>
<NavLink to="/login" className="navlinks">
Login
</NavLink>
</div>
)}
</div>
);
};
Login
const Login = () => {
const dispatch = useDispatch();
...
const handleSubmit = (e) => {
e.preventDefault();
axiosWithAuth()
.post("api/auth/login", user)
.then((res) => {
dispatch(authActions.login(res.data.token));
navigate("/posts");
})
.catch((err) => console.log(err));
};
return (
...
);
};
I'm making a MERN stack online store website and I'm fetching my products from a useEffect hook in my Shoes.js component. But I'm only getting the initial state from redux instead of the updated state.
The data is being fetched just fine but I can only access the initial state. So the values being passed to the ProductsArea component are false and null How do I get the updated state?
Here's my Shoes.js file:
import React, { useEffect } from 'react';
import './Shoes.css';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { getProducts } from '../../../actions/productsActions';
import ProductsArea from './ProductsArea';
import Navbar from '../landing/Navbar';
import Search from './Search';
export const Shoes = (props) => {
useEffect(() => {
props.getProducts();
console.log(props.products);
console.log(props.loading);
}, []);
if(props.loading) {
return (
<h1>loading</h1>
)
}
else {
return (
<div>
<Navbar />
<div className="shoes">
<Search />
<h1 className="productsTitle">Our Selection</h1>
<ProductsArea loading={props.loading} products={props.products} />
{/* {
props.products.map(product => (
<ProductCard key={product._id} product={product} />
))
} */}
</div>
</div>
)
}
}
const mapStateToProps = state => ({
products: state.products.products,
loading: state.products.loading
})
export default connect(mapStateToProps, { getProducts })(Shoes);
Here's my productsActions file
import {GET_PRODUCTS, SET_LOADING, SET_ERROR} from './types';
export const getProducts = () => async (dispatch) => {
try{
setLoading();
const res = await fetch('http://localhost:5000/products');
const data = await res.json();
console.log(data);
dispatch({
type: GET_PRODUCTS,
payload: data
});
}
catch(err) {
dispatch({
type: SET_ERROR,
payload: err
})
}
}
export const setLoading = () => {
console.log('Loading true');
return {
type: SET_LOADING
}
}
This is the getProductsReducer file:
import {GET_PRODUCTS, SET_LOADING, SET_ERROR} from '../actions/types';
const initialState = {
products: [],
loading: false,
error: null
}
export default (state = initialState, action) => {
switch (action.type) {
case GET_PRODUCTS:
console.log(action.payload);
return {
...state,
products: action.payload,
loading: false
}
case SET_LOADING:
return {
...state,
loading: true
};
case SET_ERROR:
console.log(action.payload);
return {
...state,
error: action.payload
};
default: return state;
}
}
Here's my index.js file for redux :
import {combineReducers} from 'redux';
import getProductReducer from './getProductReducer';
export default combineReducers({
products: getProductReducer
});
And the Store.js file:
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(rootReducer, initialState, composeWithDevTools(applyMiddleware(...middleware)));
export default store;
So I checked the redux extension and the state is showing up on my Home.js page but not on the Shoes.js file
Here's the Home.js file:
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { getProducts, setLoading } from '../../../actions/productsActions';
import { connect } from 'react-redux';
import {Link} from 'react-router-dom';
import './Home.css';
import Navbar from './Navbar';
export const Home = (props) => {
useEffect(() => {
props.setLoading();
props.getProducts();
//eslint-disable-next-line
console.log(props.products);
console.log(props.loading);
}, []);
if(props.loading) {
return <div>loading</div>
}
else {
return (
<div>
<Navbar />
<div className="home">
<div className="group-1">
<div className="branding">
<div className="brandName">
The
<br/>
Sole
<br/>
Store
</div>
<div>
<p>The finest designs and fits.</p>
</div>
</div>
<div className="viewProducts">
<div>
<p>
Check out our latest and greatest models
</p>
<Link className="productsBtn" to="/shoes">GO <i className="fas fa-arrow-right"/></Link>
</div>
</div>
</div>
<div className="group-2">
<div className="products">
<div className="product"></div>
<div className="product"></div>
<div className="product"></div>
<div className="product"></div>
</div>
<div className="something"></div>
</div>
</div>
</div>
)
}
}
Home.propTypes = {
products: PropTypes.object.isRequired,
loading: PropTypes.bool.isRequired
}
const mapStateToProps = state => ({
products: state.products.products,
loading: state.products.loading
});
export default connect(mapStateToProps, {getProducts, setLoading})(Home);
Although, I'm still only getting the initial state and not the updated state in the console from Home.js too.
I've made the changes that #Kalhan.Toress suggested and this is the updated Shoes.js file
import React, { useEffect } from 'react';
import './Shoes.css';
// import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { getProducts } from '../../../actions/productsActions';
import ProductsArea from './ProductsArea';
import Navbar from '../landing/Navbar';
import Search from './Search';
export const Shoes = (props) => {
useEffect(() => {
props.fetchData();
console.log(JSON.parse(props.products.products));
}, []);
if(props.loading) {
return (
<h1>loading</h1>
)
}
else {
return (
<div>
<Navbar />
<div className="shoes">
<Search />
<h1 className="productsTitle">Our Selection</h1>
<ProductsArea loading={props.loading} products={JSON.parse(props.products.products)} />
{/* {
props.products.map(product => (
<ProductCard key={product._id} product={product} />
))
} */}
</div>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
fetchData: () => dispatch(getProducts())
};
};
const mapStateToProps = state => ({
products: state.products.products,
loading: state.products.loading
})
export default connect(mapStateToProps, mapDispatchToProps)(Shoes);
I can click on the link to the Shoes page from Home and everything works perfectly, but as soon as I reload the Shoes.js page or go to it directly, this is the error I get:
Error: A cross-origin error was thrown. React doesn't have access to the actual error object in development.
This is my App.js file for the server side where I do have CORS enabled:
const express = require('express');
const app = express();
const bodyParser = require('body-parser')
const productRoute = require('./products/productRoute');
const orderRoute = require('./orders/orderRoute');
const userRoute = require('./users/userRoute');
const adminRoute = require('./admins/adminRoute');
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin','*');
res.header('Access-Control-Allow-Headers','Origin, X-Requested-With, Content-Type, Authorization, Accept');
if(res.method === 'OPTIONS') {
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, PATCH, DELETE');
}
next();
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use('/products', productRoute);
app.use('/orders', orderRoute);
app.use('/users', userRoute);
app.use('/admin', adminRoute);
app.use((req, res, next) => {
const error = new Error();
error.status = 404;
next(error);
});
app.use((error, req, res, next) => {
res.status(error.status || 500 ).json({
error: error
})
});
module.exports = app;
I'd really appreciate any help!
Thank you!
I think the way you dispatch the sync action is incorrect
by invoking props.getProducts(); it will return a sync function, that's will not trigger any dispatch action as i see
const getProducts = () => async (dispatch) => {
try{
....
to make sure it put a console.log as below and check it
useEffect(() => {
const returnedFromAction = props.getProducts();
console.log(returnedFromAction); // this should prints the returned function and it will not get dispatched
....
}
Here you need to dispatch a sync action by by executing returning function as below
You have to add a mapDispatchToProps as below
....
const mapDispatchToProps = dispatch => {
return {
fetchData: () => dispatch(getProducts())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
and then inside the useEffect use this fetchData function to dispatch the fetch action so now in useEffect
useEffect(() => {
props.fetchData();
}, []);
That will do the job for you, i have created a sample demo for you, check it out here
This will align with your approach by not using redux hooks, but theres another way that you can easily do as below.
import { useDispatch } from 'react-redux'; // import the dispatcher
const App = props => {
const dispatch = useDispatch(); // get a reference to dispatch
useEffect(() => {
dispatch(getProducts()); // dispatch the action
}, []);
see it in here
I've created a simple to do app using React. I've attempted to persist state using local storage. However, the local storage code I've added is somehow preventing my components from rendering altogether. Not only are the todos saved in state not appearing, none of my components will render. I get a blank page on refresh. Can someone help me figure out what's wrong?
Here's what happens on the initial save after the local storage code is included. It loads the components just fine, but the to dos that are already in state are not shown:
After using the form to add to dos and refreshing the page, this happens. None of the components are shown whatsoever. Just a blank page.
Here is the local storage code inside my index.js file. I'm pretty sure the problem is here but I have included the code for the other components and the reducer as well:
const persistedState = localStorage.getItem('state') ? JSON.parse(localStorage.getItem('state')) : [];
const store = createStore(reducer, persistedState);
store.subscribe(() => {
localStorage.setItem('state', JSON.stringify(store.getState()));
})
The index.js file in its entirety:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from "redux";
import { Provider } from "react-redux";
import './index.css';
import App from './App';
import { reducer } from "./reducers/todoReducer";
import * as serviceWorker from './serviceWorker';
const persistedState = localStorage.getItem('state') ? JSON.parse(localStorage.getItem('state')) : [];
const store = createStore(reducer, persistedState);
store.subscribe(() => {
localStorage.setItem('state', JSON.stringify(store.getState()));
})
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
the other components:
TodoList.js:
import Todo from "./Todo";
const TodoList = props => {
return (
<ul className="task-list">
{props.state.map(task => (
<Todo task={task} />
))}
</ul>
)
}
const mapStateToProps = state => {
return {
state: state
}
}
export default connect(mapStateToProps)(TodoList);
TodoForm.js:
const TodoForm = props => {
const [newItemText, setNewItemText] = useState("");
const handleChanges = e => {
e.preventDefault();
setNewItemText(e.target.value);
};
const saveState = () => localStorage.setItem("props.state", JSON.stringify(props.state));
useEffect(() => {
const todos = localStorage.getItem('state');
if (todos) props.setState({ [props.state]: JSON.parse(props.state) })
}, [])
return (
<div className="form-div">
<input
className="add-input"
name="todo"
type="text"
placeholder="enter a task"
value={newItemText}
onChange={handleChanges}
/>
<button
className="add-button"
onClick = {e => {
e.preventDefault();
props.addItem(newItemText);
saveState();
}}>Add a Task
</button>
<button
className="add-button"
onClick={e => {
e.preventDefault();
props.removeCompleted();
}}>Remove Completed
</button>
</div>
)
}
const mapStateToProps = state => {
return {
state: state
}
}
export default connect(mapStateToProps, {addItem, removeCompleted})(TodoForm);
Todo.js:
const Todo = props => {
return (
<li
className="tasks"
style={{textDecoration: props.task.completed ? 'line-through' : 'none'}}
onClick={() => props.toggleCompleted(props.task.id)}>
{props.task.item}
</li>
)
}
const mapStateToProps = state => {
return {
state: state
}
}
export default connect(mapStateToProps, {toggleCompleted})(Todo);
todoReducer.js:
export const initialState = [
{ item: 'Learn about reducers', completed: false, id: 1 },
{ item: 'review material from last week', completed: false, id: 2 },
{ item: 'complete reducer todo project', completed: false, id: 3 }
]
export const reducer = (state = initialState, action) => {
switch(action.type) {
case ADD_ITEM:
// console.log(action.payload)
return [
...state,
{
item: action.payload,
completed: false,
id: Date.now()
}
]
case TOGGLE_COMPLETED:
const toggledState = [...state];
toggledState.map(item => {
if(item.id === action.payload) {
item.completed = !item.completed;
}
})
console.log(toggledState);
state = toggledState;
return state;
case REMOVE_COMPLETED:
return state.filter(item => !item.completed);
default:
return state;
}
}
export default reducer;
App.js:
import React from 'react';
import './App.css';
// components
import TodoList from "./components/TodoList";
import TodoForm from "./components/TodoForm";
function App() {
return (
<div className="App">
<h1 className="title">To Do List</h1>
<TodoList />
<TodoForm />
</div>
);
}
export default App;
actions.js:
export const ADD_ITEM = 'ADD_ITEM';
export const TOGGLE_COMPLETED = 'TOGGLE_COMPLETED';
export const REMOVE_COMPLETED = 'REMOVE_COMPLETED';
export const addItem = input => {
return {
type: ADD_ITEM, payload: input
}
};
export const toggleCompleted = (id) => {
return {
type: TOGGLE_COMPLETED, payload: id
}
};
export const removeCompleted = () => {
return {
type: REMOVE_COMPLETED
}
};
I get my action called in Redux Dev Tools and even the new state, but in the actual Component props is undefined.
The component:
import React, { useEffect } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { getPromos } from '../../actions/promo';
import PropTypes from 'prop-types';
const Landing = ({ getPromos, data }) => {
useEffect(() => {
getPromos();
console.log(data) // ==>> "UNDEFINED"
}, []);
return (
<div>
<section className='landing'>
<div className='dark-overlay'>
<div className='landing-inner'>
<h1 className='x-large'> Developer Connector </h1>
<p className='lead'>
Create a developer profile/portfolio, share posts and get help
from other developers
</p>
<div className='buttons'>
<Link to='/register' className='btn btn-primary'>
Sign Up
</Link>
<Link to='/login' className='btn btn-light'>
Login
</Link>
</div>
</div>
</div>
</section>
</div>
);
};
Landing.propTypes = {
getPromos: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
data: state.data
});
export default connect(
mapStateToProps,
{ getPromos }
)(Landing);
Actions:
import axios from 'axios';
import { setAlert } from './alert';
import { GET_PROMOS, REGISTER_FAIL } from './types';
export const getPromos = () => async dispatch => {
try {
const res = await axios.get('/api/promo');
dispatch({
type: GET_PROMOS,
payload: res.data
});
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({ type: REGISTER_FAIL });
}
};
And reducer:
import { GET_PROMOS } from '../actions/types';
const initialState = {
data: null,
title: ''
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_PROMOS:
return { ...state, data: payload };
default:
return state;
}
}
Like I said, in Redux Dev Tools I get my desired output. But for some reason I cant get to echo this state in the component. What im getting wrong? Can it be something about the hooks?
Thanks !
First thing that jumps at me is that you have a naming conflict with the getPromos in your component, it's defined in the imports as getPromos then it's destructured in the component as { getPromos } as well. I'm surprised you didn't get an error there for naming conflicts.
You will want to NOT destructure getPromos in the component and instead call it as (props) => { props.getPromos } to actually call the connected action creator instead of the unconnected one.
Second, Is that reducer the main root reducer? or is it nested in the root reducer? if the latter is true then in your mapStateToProps the data prop should be one level deeper, as in state: state.rootLevelState.data
(sorry can't ask questions in the comments due to reputation < 50)
enter image description here
Here's a screenshot of the redux dev tools
I have been flipping through search results and googling like an addict chasing that amber baby rattle. I am finding nothing which uses React-Router -v 4. I am building a simple(lol simple in theory) application which uses axios to pass user profiles to a Home component which then maps the response and displays a gird of avatars and names. This works fine. The problem is, I want to click on one of those avatars then pass that specific data which is associated with that avatar to a new individual user page by using the id params. So click on avatar open user profile. I know before react-router -v 4 it seems you could actually just pass a method directly into a route but this is not working for me. Any help would greatly help me with this puzzle.
Action
import axios from 'axios'
const URI = '###'
export function fetchUsers () {
return function (dispatch) {
axios.get (URI)
.then ((response) => {
dispatch({
type: 'FETCH_USERS_FULFILLED',
payload: response.data.users
})
})
.catch ((err) => {
dispatch({
type: 'FETCH_USERS_REJECTED',
payload: err
})
})
}
}
Reducer
const initialState = {
users: [],
fetching: false,
fetched: false,
error: null
}
export default function reducer (state = initialState, action) {
switch (action.type) {
case 'FETCH_USERS_LOADING': {
return {
...state,
fetching: true,
fetched: false,
users: action.payload
}
}
case 'FETCH_USERS_FULFILLED': {
return {
...state,
fetching: false,
fetched: true,
users: action.payload
}
}
case 'FETCH_USERS_REJECTED': {
return {
...state,
fetching: false,
error: action.payload
}
}
}
return state
}
Store
import { applyMiddleware, createStore, compose } from 'redux'
import { createLogger } from 'redux-logger'
import thunk from 'redux-thunk'
import reducers from '../reducers'
const middleware = applyMiddleware(thunk, createLogger())
const store = createStore(
reducers,
compose(middleware, window.devToolsExtension ?
window.devToolsExtension() : f => f)
)
export default store
Home
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Profile from '../Profile'
import { fetchUsers } from '../redux/actions/userActions'
class Home extends Component {
constructor(props) {
super(props);
this.state = {
}
}
componentDidMount() {
this.props.fetchUsers();
}
render () {
return (
<div className={'view-wrapper'}>
<div className={'home-container'}>
{this.props.users.map((user, i) =>
<Profile {...this.props} key={i} i={i} user={user}/>
)}
</div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
users: state.user.users,
fetched: state.user.fetched,
error: state.user.error
}
};
const mapDispatchToProps = (dispatch) => {
return {
fetchUsers: () => dispatch(fetchUsers())
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Profile
import React from 'react'
import { Link } from 'react-router-dom'
const Profile = (props) => (
<div className='profile-container'>
<Link to={`/profile/${props.id}`}>
<img
src={props.avatar}
className={'user-avatar'}
alt={props.name}
/>
</Link>
</div>
)
export default Profile
Routes
<Switch>
<Route exact path='/' component={Home} />
<Route path='/profile/:id' component={Profile}/>
</Switch>
You're half way there. You don't need to pass the data (props) along to the individual user page, instead, use the id in the route so the individual user page knows what record in the Store it wants. In your individual user component:
const mapStateToProps = (state, props) => {
return {
user: state.user.users.find( (user) => (user.id === props.match.params.id) )
};
};
Then you can access 'user' as a prop in the component.