I am new in react.I am trying to use react-redux style from the beginning.
Below is what I tried for a simple product listing page.
In my App.js for checking if the user is still logged in.
class App extends Component {
constructor(props) {
super(props);
this.state = {}
}
componentDidMount() {
if (isUserAuthenticated() === true) {
const token = window.localStorage.getItem('jwt');
if (token) {
agent.setToken(token);
}
this.props.appLoad(token ? token : null, this.props.history);
}
}
render() {
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
isUserAuthenticated() === true
? <Component {...props} />
: <Redirect to='/logout' />
)} />
)
return (
<React.Fragment>
<Router>
<Switch>
{routes.map((route, idx) =>
route.ispublic ?
<Route path={route.path} component={withLayout(route.component)} key={idx} />
:
<PrivateRoute path={route.path} component={withLayout(route.component)} key={idx} />
)}
</Switch>
</Router>
</React.Fragment>
);
}
}
export default withRouter(connect(mapStatetoProps, { appLoad })(App));
In my action.js appLoaded action is as under
export const appLoad = (token, history) => {
return {
type: APP_LOAD,
payload: { token, history }
}
}
reducer.js for it
import { APP_LOAD, APP_LOADED, APP_UNLOADED, VARIFICATION_FAILED } from './actionTypes';
const initialState = {
appName: 'Etsync',
token: null,
user: null,
is_logged_in: false
}
const checkLogin = (state = initialState, action) => {
switch (action.type) {
case APP_LOAD:
state = {
...state,
user: action.payload,
is_logged_in: false
}
break;
case APP_LOADED:
state = {
...state,
user: action.payload.user,
token: action.payload.user.token,
is_logged_in: true
}
break;
case APP_UNLOADED:
state = initialState
break;
case VARIFICATION_FAILED:
state = {
...state,
user: null,
}
break;
default:
state = { ...state };
break;
}
return state;
}
export default checkLogin;
And in Saga.js I have watched every appLoad action and performed the operation as under
import { takeEvery, fork, put, all, call } from 'redux-saga/effects';
import { APP_LOAD } from './actionTypes';
import { appLoaded, tokenVerificationFailed } from './actions';
import { unsetLoggeedInUser } from '../../helpers/authUtils';
import agent from '../agent';
function* checkLogin({ payload: { token, history } }) {
try {
let response = yield call(agent.Auth.current, token);
yield put(appLoaded(response));
} catch (error) {
if (error.message) {
unsetLoggeedInUser();
yield put(tokenVerificationFailed());
history.push('/login');
} else if (error.response.text === 'Unauthorized') {
unsetLoggeedInUser();
yield put(tokenVerificationFailed());
}
}
}
export function* watchUserLogin() {
yield takeEvery(APP_LOAD, checkLogin)
}
function* commonSaga() {
yield all([fork(watchUserLogin)]);
}
export default commonSaga;
After that for productLists page my code is as under
//importing part
class EcommerceProductEdit extends Component {
constructor(props) {
super(props);
this.state = {}
}
componentDidMount() {
**//seeing the props changes**
console.log(this.props);
this.props.activateAuthLayout();
if (this.props.user !== null && this.props.user.shop_id)
this.props.onLoad({
payload: Promise.all([
agent.Products.get(this.props.user),
])
});
}
render() {
return (
// JSX code removed for making code shorter
);
}
}
const mapStatetoProps = state => {
const { user, is_logged_in } = state.Common;
const { products } = state.Products.products.then(products => {
return products;
});
return { user, is_logged_in, products };
}
export default connect(mapStatetoProps, { activateAuthLayout, onLoad })(EcommerceProductEdit);
But in this page in componentDidMount if I log the props, I get it three time in the console. as under
Rest everything is working fine. I am just concerned,the code i am doing is not up to the mark.
Any kinds of insights are highly appreciated.
Thanks
It's because you have three state updates happening in ways that can't batch the render.
You first render with no data. You can see this in the first log. There is no user, and they are not logged in.
Then you get a user. You can see this in the second log. There is a user, but they are not logged in.
Then you log them in. You can see this in the third log. There is a user, and they are logged in.
If these are all being done in separate steps and update the Redux store each step you'll render in between each step. If you however got the user, and logged them in, and then stored them in the redux state in the same time frame you'd only render an additional time. Remember React and Redux are heavily Async libraries that try to use batching to make sure things done in the same time frame only cause one render, but sometimes you have multiple network steps that need to be processed at the same time. So no you're not doing anything wrong, you just have a lot of steps that can't easily be put into the same frame because they rely on some outside resource that has its own async fetch.
Related
I have a redux State HOC to manage the connection
I Have a problem when I add a new post to the store
import React, { useEffect } from "react";
import { connect } from "react-redux";
export default function withState(WrappedComponent) {
function mapStateToProps(reduxState) {
let state = {};
for(let t of Object.entries(reduxState)) {
state = {...state, ...t[1]}
}
return {
...state,
};
}
return connect(
mapStateToProps,
null
)(function (props) {
useEffect(() => {}, [props.posts, props.comments]) /*tried this but didn't work*/
return (
<React.Fragment>
<WrappedComponent {...props} />
</React.Fragment>
);
});
}
I am trying to make the program render the response from my back-end without me reloading the page manually
I tried using the useEffect
and I saw through the dev tools that the state change correctly
my reducer
import { GET_ALL_POSTS, CREATE_NEW_POST } from "../actions"
const initialState = {
posts: []
}
export default function postReducer(state = initialState, action) {
let newState = {...state}
switch(action.type){
case GET_ALL_POSTS:
return {
...newState,
posts: [...action.posts],
}
case CREATE_NEW_POST:
const posts = [...newState.posts, action.post]
return {
...newState,
posts
}
default:
return {
...newState,
}
}
}
I also read that react changes doesn't respond to shallow copies so I changed the whole array in the post reduces when I add a new post
Your withState HOC is very strange. I'm not sure why you don't just use connect directly (or use hooks). But try this:
export function withState(WrappedComponent) {
return connect(
(state) => ({
posts: state.postsReducer.posts,
comments: state.commentsReducer.comments
}),
null
)(WrappedComponent);
}
I have followed a tutorial that centralizes React app error management with Redux in an errorReducer file:
errorReducer.js
import { GET_ERRORS } from "../actions/types";
const initialState = {};
export default function (state = initialState, action) {
switch (action.type) {
case GET_ERRORS:
return action.payload;
default:
return state;
}
}
That way, whenever there's an error in any http request, the content of the error is stored only in one place in the app:
authActions.js
export const registeruser = (userData, history) => (dispatch) => {
axios
.post("/api/users/register", userData)
.then((res) => {
history.push("/login-page");
})
.catch((err) => {
dispatch({
type: GET_ERRORS,
payload: err.response.data,
});
});
};
And this is how the error response is managed in Register component:
RegisterPage.js
export class RegisterPage extends Component {
constructor() {
super();
this.state = {
errors: {},
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({ errors: nextProps.errors });
}
}
render() {
const { errors } = this.state;
return (
<>
<div>
{Object.values(errors).map((value) => (
<p>{value}</p>
))}
</div>
</>
);
}
}
RegisterPage.propTypes = {
errors: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
errors: state.errors,
});
export default connect(mapStateToProps, { registeruser })(
withRouter(RegisterPage)
);
#NOTE: I only displayed the code that is relevant to error management.
The same logic is applied for all components.
Sometimes, I would like to display a text when a request is succesful and not necessarily perform an action like the one above:
history.push("/login-page");
What I would like to know is:
Does it make sense to create a success reducer, similar to the error reducer, that I can use to centralize the logic to be executed when any http request has been successful inside React components and display a message accordingly.
I' learning to work with Redux, React & React Router.
I've face with such a problem:
When I redirect from "/" to the URL like "/details/{id}" using Link - I see that a wrong action creator is called. Action creator from "/" component is called indead of component's one in "/details/{id}.
But if I refresh my page, everything will be fine. Correct action is called.
Routing with Link: <Link to={/details/${this.props.movie.id}}>
Other bug: if I press "Back" from this page to return to "/", I will get an error, that my props' data are undefined.
Like, props are empty and the action creator for http-request is not called (?) from componentDidMount().
But if I refresh my page again, everything will be fine again.
What's wrong with routing?? Or redux?
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<Routes />
</BrowserRouter>
</Provider>,
document.getElementById('root'));
const Routes = () => {
return <div>
<Route exact path="/" component={Main} />
<Route path="/details/:id" component={MovieDetailsContainer} />
</div>;
}
"/":
class MoviesDiscover extends Component {
componentDidMount() {
this.props.dicoverMovies();
}
render() {
...
}
}
const mapStateToProps = (state) => {
return {
items: state.movieItems,
hasErrored: state.movieHasErrored,
isLoading: state.movieIsLoading,
};
};
const mapDispatchToProps = (dispatch) => {
return {
dicoverMovies: (url) => dispatch(dicoverMovies(url))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(MoviesDiscover);
"details/{id}":
class MovieDetailsContainer extends Component {
componentDidMount() {
var id = this.props.match.params.id;
this.props.getMovie(id); // called from MoviesDiscover instead of this, after routing
}
render() {
var id = this.props.match.params.id;
...
}
}
const mapStateToProps = (state) => {
return {
item: state.movieItems,
hasErrored: state.movieHasErrored,
isLoading: state.movieIsLoading,
};
};
const mapDispatchToProps = (dispatch) => {
return {
getMovie: (url) => dispatch(getMovieDetails(url))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(MovieDetailsContainer);
Actions (the same for two reqests - actions for results, error and lodaing):
...
export function moviesDataSuccess(items) {
return {
type: MOVIES_RESULTS,
isLoading: false,
items: items
};
}
export function dicoverMovies() {
return (dispatch) => {
dispatch(moviesIsLoading(true));
API.get(URL_MOVIE_DISCOVER)
.then(response => {
dispatch(moviesIsLoading(false));
dispatch(moviesDataSuccess(response.data));
})
.catch(e => {
console.log(e);
dispatch(moviesHasErrored(true))
});
};
}
export function getMovieDetails(id) {
return (dispatch) => {
dispatch(moviesIsLoading(true));
API.get(URL_MOVIE_DETAILS(id))
.then(response => {
dispatch(moviesIsLoading(false));
dispatch(moviesDataSuccess(response.data));
})
.catch(e => {
console.log(e);
dispatch(moviesHasErrored(true))
});
};
}
Reducers:
export function movieHasErrored(state = false, action) {
switch (action.type) {
case MOVIES_ERROR:
return action.hasErrored;
default:
return state;
}
}
export function movieIsLoading(state = false, action) {
switch (action.type) {
case MOVIES_LOADING:
return action.isLoading;
default:
return state;
}
}
export function movieItems(state = {}, action) {
switch (action.type) {
case MOVIES_RESULTS:
return action.items;
default:
return state;
}
}
const rootReducer = combineReducers({
movieHasErrored,
movieIsLoading,
movieItems
});
I will be happy to all the recommendations. Thank you.
React router matches the closes URL first, I think the issue is the order of your component.
I suggest you order them like this and it should get resolved:
const Routes = () => {
return <div>
<Route path="/details/:id" component={MovieDetailsContainer} />
<Route exact path="/" component={Main} />
</div>;
}
Also React Router has a component for switching between URLs that you can take advantage if your URLs are ambiguous:
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
...
<Switch>
<Route path="/about" component={About} />
<Route path="/company" component={Company} />
<Route path="/:user" component={User} />
</Switch>
PS: Sorry I just wanted to comment instead of posting this answer if this is not the answer you're looking for, I just don't yet have reputation to comment directly :|
My mistake was in other place.
The problem was, I was using the same action-type for list results and details reasult.
When I was opening a page for details results, I've got props from previous page with list results ('cause they use the same action-type). Before my Details Component would fetch new data.
And my code was failing in render with wrong data.
without redux it works so that not a api connection problem
I have an express app connected to react with proxy I have already managed to display my data in react but now i want to make that in redux soo:
There is my problem, i have maked all the reducers/action, store and combine reducer but I didn't see any datas in my page and i haven't any errors
There is my code :
Action
export const api = ext => `http://localhost:8080/${ext}`;
//
// ─── ACTION TYPES ───────────────────────────────────────────────────────────────
//
export const GET_ADVERTS = "GET_ADVERTS";
export const GET_ADVERT = "GET_ADVERT";
//
// ─── ACTION CREATORS ────────────────────────────────────────────────────────────
//
export function getAdverts() {
return dispatch => {
fetch("adverts")
.then(res => res.json())
.then(payload => {
dispatch({ type: GET_ADVERTS, payload });
});
};
}
export function getAdvert(id) {
return dispatch => {
fetch(`adverts/${id}`)
.then(res => res.json())
.then(payload => {
dispatch({ type: GET_ADVERT, payload });
});
};
}
reducer
import { combineReducers } from "redux";
import { GET_ADVERTS, GET_ADVERT } from "../actions/actions";
const INITIAL_STATE = {
adverts: [],
advert: {}
};
function todos(state = INITIAL_STATE, action) {
switch (action.type) {
case GET_ADVERTS:
return { ...state, adverts: action.payload };
case GET_ADVERT:
return { advert: action.payload };
default:
return state;
}
}
const todoApp = combineReducers({
todos
});
export default todoApp;
index.js
//imports
const store = createStore(todoApp, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById("app")
);
My advertlist page :
//imports..
class Adverts extends Component {
componentDidMount() {
this.props.getAdverts();
}
render() {
const { adverts = [] } = this.props;
return (
<div>
<Header />
<h1>Adverts</h1>
{adverts.map(advert => (
<li key={advert._id}>
<a href={"adverts/" + advert._id}>
{advert.name} {advert.surname}
</a>
</li>
))}
<Footer />
</div>
);
}
}
const mapStateToProps = state => ({
adverts: state.adverts
});
export default connect(
mapStateToProps,
{ getAdverts }
)(Adverts);
I think your problem is here:
function mapStateToProps(state) {
return {
**adverts: state.adverts**
};
}
It should work if you change state.adverts to state.todos.adverts:
function mapStateToProps(state) {
return {
adverts: state.todos.adverts
};
}
Because your reducer is called todos, and it has state { adverts }, that's why you cannot access adverts even tho they are obtained.
You can check out working version here: https://codesandbox.io/s/olqxm4mkpq
The problem is, when you just create a store with one reducer without using combine reducer, it is possible to refer it directly in the ContainerS, like this:
const mapStateToProps = state => {
return{
*name of var*: state.adverts /*direct refers to adverts*/
}
}
But, when it use combined-reducer , it has to refer to an exact reducer that you want to use.like this :
const mapStateToProps = state => {
return{
*name of var* : state.todos.adverts (indirect refers to adverts from combined-reducer todos)
}
}
I am implementing a project where the data going to be shared in different components. So I decided to use redux-react for state management.
I used redux react async api call to get data from api. However I got undefined when the component mounted for the first time and returned actual data.
However, when I tried to implement some function on returned data, I got this error:
"Cannot read property of undefined"
I can see the state in redux developer tools and it has data and the logs function display action correctly. I can not understand why I am getting undefined. Here is my code:
const initialState = {
candidate: {},
companies: [],
offers: [],
moreStatehere:...
}
Reducer for the candidate
export default function profileReducer(state = initialState, action) {
switch(action.type) {
case FETCH_POSTS_FAILURE:
return Object.assign({}, state, {
didInvalidate: true
})
case REQUEST_PROFILE:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
})
case RECEIVE_PROFILE:
return {
...state,
candidate: action.data
}
default:
return state;
}
}
root reducer
const rootReducer = combineReducers({
profiles: profileReducer
})
export default rootReducer;
create store
const composeEnhanser = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__||compose;
const loggerMiddleware = createLogger()
export default function configureStore() {
return createStore(
rootReducer,
composeEnhanser(applyMiddleware(thunkMiddleware,
loggerMiddleware))
);
}
index.js
const store = configureStore();
const app = (
<Provider store= {store}>
<BrowserRouter>
<App/>
</BrowserRouter>
</Provider>
)
ReactDOM.render(app, document.getElementById('root'));
registerServiceWorker();
action creator/api call
export function feachProfiles() {
return function (dispatch) {
dispatch(requestProfile)
return fetch(API_URL)
.then(
response => response.json(),
error => console.log('An error occurred.', error)
)
.then(json =>
dispatch(receiveProfile(json))
)
}
}
componentuse
class CandidatesList extends Component {
constructor (props){
super (props)
}
componentWillMount() {
this.props.feachProfiles();
}
handleClick() {
}
componentWillUnmount() {
}
render() {
const candidate = this.props.profiles.map(profile=>(
<div> </div>
));
return (
<div>
<ViewCandidate
/>
</div>
);
}
}
const mapStateToProps = state => {
return {
profiles: state.profiles.candidate || []
}
}
const mapDispatchToProps = (dispatch) => {
return {
feachProfiles: bindActionCreators(feachProfiles, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CandidatesList);
action RECEIVE_PROFILE #
redux-logger.js:1 prev state {profiles: {…}}
redux-logger.js:1 action {type: "RECEIVE_PROFILE", data: {…}}
redux-logger.js:1 next state {profiles: {…}}
make sure to write this just before map function
if (this.props.profiles.length === 0) return null;
this.props.profiles should have array length of greater than 0
const candidate = this.props.profiles.map(profile=>(
<div> </div>
));