React/Redux ReferenceError: Cannot access 'Action' before initialization - reactjs

I have made a React site using redux which is successfully working on one component.
This is mapped to props and pulls data from redux using the fetchposts.
This looks like this.
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { fetchPosts, fetchItins } from "../../../actions/postActions";
import TravelAlerts from "./TravelAlerts//travelAlert";
import IntelligenceAlerts from "./IntelligenceAlerts/IntelligenceAlert";
import AllAlerts from "./AllAlerts/AllAlerts";
import "./Alerts.css";
class Alerts extends Component {
state = {
showAllAlerts: true,
showAllIntelligenceAlerts: false,
showAllTravellers: false,
currentPage: 1,
alertsPerPage: 20,
allAlerts: [],
dataFetched: false,
};
//usiing redux action which is mapped to this compoenent
componentDidMount() {
this.props.fetchPosts();
}
render() {
return (
<div>
<hr />
<div>
{this.state.showAllAlerts ? (
<>
<AllAlerts all={this.Sorter()} />
</>
) : (
<></>
)}
</div>
<>
{this.state.showAllTravellers ? (
<>
<></>
<TravelAlerts alerts={this.props.posts.travelAlerts} />
</>
) : (
<></>
)}
</>
<>
{this.state.showAllIntelligenceAlerts ? (
<>
<IntelligenceAlerts
alerts={this.props.posts.intelligenceAlerts}
/>
</>
) : (
<></>
)}
</>
</div>
);
}
}
Alerts.propTypes = {
fetchPosts: PropTypes.func.isRequired,
posts: PropTypes.object.isRequired,
// newPost: PropTypes.object
};
const mapStateToProps = (state) => ({
posts: state.posts.items,
// newPost: state.posts.item
});
export default connect(mapStateToProps, { fetchPosts })(Alerts);
This works fine and does successfully get information. However when doing this (Which is essentially exactly the same it throws this error).
ReferenceError: Cannot access 'fetchItins' before initialization
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { fetchPosts, fetchItins } from "../../../actions/postActions";
import ItineraryNew from "./ItineraryNew/ItineraryNew";
import ItineraryAll from "./ItineraryAll/ItineraryAll";
import ItineraryArrivals from "./ItineraryArrivals/ItineraryArrivals";
import ItineraryDepatures from "./ItineraryDepatures/ItineraryDepatures";
class Itinerary extends Component {
state = {
showAllItins: true,
showAllItinsArrivals: false,
showAllItinsDepatures: false,
showAllItinsNew: false,
currentPage: 1,
alertsPerPage: 20,
};
//usiing redux action which is mapped to this compoenent
componentDidMount() {
this.props.fetchItins();
}
//navigation helper
DisableAlerts() {
this.setState({
showAllItins: false,
showAllItinsArrivals: false,
showAllItinsDepatures: false,
showAllItinsNew: false,
});
}
//pagination change page
handleClick(number) {
this.setState({
currentPage: number,
});
}
ToggleItin(name) {
this.DisableAlerts();
if (name === "All") {
this.setState({ showAllItins: true });
} else if (name === "Arrivals") {
this.setState({ showAllItinsArrivals: true });
} else if (name === "Depatures") {
this.setState({ showAllItinsDepatures: true });
} else if (name === "New") {
this.setState({ showAllItinsNew: true });
} else {
this.setState({ showAllItins: true });
}
}
render() {
{
console.log(this.props.posts);
}
return (
<div>
<button style={{ width: "18%" }} onClick={() => this.ToggleItin("All")}>
ALL Travel
</button>
<button
style={{ width: "18%" }}
onClick={() => this.ToggleItin("Arrivals")}
>
Arrivals
</button>
<button
style={{ width: "18%" }}
onClick={() => this.ToggleItin("Depatures")}
>
Depatures
</button>
<button style={{ width: "18%" }} onClick={() => this.ToggleItin("New")}>
New
</button>
<br />
<hr />
<>
{this.state.showAllItins ? (
<>
<ItineraryAll itinerary={this.props.posts} />
</>
) : (
<></>
)}
</>
<>
{this.state.showAllItinsArrivals ? (
<>
<ItineraryArrivals itinerary={this.props.posts} />
</>
) : (
<></>
)}
</>
<>
{this.state.showAllItinsDepatures ? (
<>
<ItineraryDepatures itinerary={this.props.posts} />
</>
) : (
<></>
)}
</>
<>
{this.state.showAllItinsNew ? (
<>
<ItineraryNew itinerary={this.props.posts} />
</>
) : (
<></>
)}
</>
</div>
);
}
}
Itinerary.propTypes = {
fetchItins: PropTypes.func.isRequired,
posts: PropTypes.array.isRequired,
// newPost: PropTypes.object
};
const mapStateToProps = (state) => ({
posts: state.itin.itins,
// newPost: state.posts.item
});
export default connect(mapStateToProps, { fetchItins })(Itinerary);
So I thought I would try to completely replace the itinerary component with all the initial component so that the data is exactly the same and just change its name to itinerary(as first component worked perfectly).
However when I did this it errored
ReferenceError: Cannot access 'fetchPosts' before initialization
Which was interesting to me as it works fine within the other component and now the code is essentially exactly the same.
Below is the store/actions and reducer
reducer
import { FETCH_POSTS, NEW_POST, FETCH_ITINS } from "../actions/types";
const initialState = {
items: {},
item: {},
itins: {},
travelAlerts: [],
intelligenceAlerts: [],
};
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_POSTS:
return {
...state,
items: action.payload,
travelAlerts: action.payload.travelAlerts,
intelligenceAlerts: action.payload.intelligenceAlerts,
};
// case NEW_POST:
// return {
// ...state,
// item: action.payload,
// };
case FETCH_ITINS:
return {
...state,
itins: action.payload,
};
default:
return state;
}
}
store
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
reducer
import Itinerary from "../components/SideNav/Itinerary/Itinerary";
import { FETCH_POSTS, NEW_POST, FETCH_ITINS } from "./types";
export const fetchPosts = () => (dispatch) => {
fetch(
"a url im using"
)
.then((res) => res.json())
.then((posts) =>
dispatch({
type: FETCH_POSTS,
payload: posts,
})
);
};
export const fetchItins = () => (dispatch) => {
fetch("https://jsonplaceholder.typicode.com/todos")
.then((res) => res.json())
.then((itin) =>
dispatch({
type: FETCH_ITINS,
payload: itin,
})
);
};
export const createPost = (postData) => (dispatch) => {
fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify(postData),
})
.then((res) => res.json())
.then((post) =>
dispatch({
type: NEW_POST,
payload: post,
})
);
};
I would really appreciate any help on this as I am at a loss.

Related

React: useContext is not updating current state

i'm actually stuck, i'm trying to find a way to centralize data into my app.
When i'm clicking on the button, isDisplay is supposed to be true ; the state is changing in my context file but not into the app.
thx !
Button.tsx
const Button = () => {
const { state, dispatch } = useContext(AppContext);
const { isDisplay } = state;
return (
<Fragment>
<BootstrapButton
onClick={() => {
dispatch({
type: "DISPLAY_USERS",
payload: state.users,
});
}}
variant={isDisplay ? "success" : "primary"}
>
{isDisplay ? "Albums chargés!" : "Charger les albums"}
</BootstrapButton>
</Fragment>
);
};
export default Button;
reducer.ts
import { RawUser } from "../interfaces";
import { InitialStateType } from "./context";
type ActionMap<M extends { [index: string]: any }> = {
[Key in keyof M]: M[Key] extends undefined
? {
type: Key;
}
: {
type: Key;
payload: M[Key];
};
};
type UsersPayload = {
["LOAD_USERS"]: RawUser[];
["DISPLAY_USERS"]: RawUser[];
};
export type LoadUsersActions =
ActionMap<UsersPayload>[keyof ActionMap<UsersPayload>];
export const loadUsersReducer = (
state: InitialStateType,
action: LoadUsersActions
) => {
switch (action.type) {
case "LOAD_USERS":
return {
...state,
users: action.payload,
isLoading: true,
};
case "DISPLAY_USERS":
return {
...state,
isDisplay: true,
};
default:
return state;
}
};
context.tsx
export type InitialStateType = {
users: RawUser[];
isLoading: boolean;
isDisplay: boolean;
};
export const initialState = {
users: [],
isLoading: true,
isDisplay: false,
};
const AppContext = createContext<{
state: InitialStateType;
dispatch: Dispatch<LoadUsersActions>;
}>({
state: initialState,
dispatch: () => null,
});
const mainReducer = (data: InitialStateType, action: LoadUsersActions) => ({
data: loadUsersReducer(data, action),
});
const AppProvider: FC = ({ children }) => {
const [state, dispatch] = useReducer(mainReducer, initialState as never);
useEffect(() => {
axios.get("https://jsonplaceholder.typicode.com/users").then((result) => {
dispatch({ type: "LOAD_USERS", payload: result.data });
});
}, []);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};
export { AppProvider, AppContext };
App.tsx
import React, { useContext, useEffect, useState } from "react";
import Jumbotron from "react-bootstrap/Jumbotron";
import Container from "react-bootstrap/Container";
import DefaultButton from "./components/button/Button";
import UserCards from "./components/cards/UserCardsPlaceHolder";
import { AppContext, AppProvider } from "./context/context";
import UsersLoaded from "./components/cards/UsersLoaded";
import { UseGetUsers } from "./api/usersList";
function App() {
const { state } = useContext(AppContext);
const { isDisplay } = state;
console.log(state);
return (
<AppProvider>
<main className="main">
<Jumbotron fluid>
<Container fluid="md">
<h1 className="mb-5">Keep calm, take a deep breath...</h1>
<DefaultButton />
</Container>
</Jumbotron>
<Container fluid="md">
{isDisplay ? <UsersLoaded /> : <UserCards />}
</Container>
</main>
</AppProvider>
);
}
export default App;
It looks like in App.tsx you are accessing an undefined value isDisplay - does it compile?
it should be
const { state: { isDisplay } } = useContext(AppContext);
instead of
const { state } = useContext(AppContext);

React State Undefined

The problem is that when I first Logged-in, The userId Variable on renderList() or the this.props.user will always be null. it will work when I refreshed it. I tried checking it on the first line of renderList function but it seems it will always be null after I logged in. I even tried to dispatch the fetchBlog actions before redirecting after logging in successfully.
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { Card, Button } from 'react-bootstrap';
import { fetchBlogs, deleteBlog } from '../../actions';
class FetchBlogs extends React.Component{
componentDidMount(){
this.props.fetchBlogs();
}
renderButtons(blog, userId){
if(blog._id = userId){
return (
<div>
<Button as={Link} to={`/blogs/edit/${blog._id}`} className="mx-2" variant="outline-warning" size="md">Edit</Button>
<Button onClick={() => {
this.props.deleteBlog(blog._id)
}}
className="mx-2"
variant="outline-danger"
size="md">
Delete
</Button>
</div>
);
}
return '';
}
renderList(){
const userId = this.props.user && this.props.user._id;
return this.props.blogs.map(blog => {
return (
<Card className="m-2" key={blog.title}>
<Card.Body>
<Card.Title as={Link} to={`/blogs/${blog._id}`}>{blog.title}</Card.Title>
<Card.Text>{blog.content}</Card.Text>
</Card.Body>
<div className="mb-2">
{this.renderButtons(blog, userId)}
</div>
</Card>
);
})
}
render(){
return (
<div>
<h2 className="m-2">My Blogs</h2>
{this.renderList()}
</div>
);
}
}
const stateToProps = state => {
return {
blogs: Object.values(state.blogs),
user: state.auth.user,
}
}
export default connect(stateToProps, { fetchBlogs, deleteBlog, })(FetchBlogs);
This is the code for my action login
export const login = formValues => async dispatch => {
const config = {
header: {
'Content-Type': 'application/json'
}
};
try{
const res = await portfolios.post('/auth/login', formValues, config)
dispatch({
type: 'LOGIN_SUCCESS',
payload: res.data
});
dispatch(fetchBlogs())
history.push('/blogs/all');
}catch(e){
dispatch({
type: 'LOGIN_FAILED',
});
history.push('/auth/login');
}
}
This is my Auth Reducer
const INITIAL_STATE = {
access: localStorage.getItem('access'),
isAuthenticated: null,
isLoading: false,
user: null,
}
const authReducer = (state = INITIAL_STATE, action) => {
switch(action.type){
case 'LOGIN_SUCCESS':
case 'REGISTER_SUCCESS':
localStorage.setItem("access", action.payload.token);
return {
...state,
access: action.payload.token,
user: action.payload.user,
isAuthenticated: true,
isLoading: false
};
case 'AUTH_ERROR':
case 'LOGIN_FAILED':
case 'LOGOUT_SUCCESS':
case 'REGISTER_FAIL':
localStorage.removeItem('access');
return {
...state,
user: null,
isAuthenticated: false,
isLoading: false,
}
default:
return state;
}
}
I don't know what am I missing. Ive checked and Searched some answers and I did check if the prop is null but still no changes

How to use React Redux Hooks to load spinners

I am trying to load spinner using react-redux hooks (useSelector and useDispatch). I am able to fetch data but not loader (in my case showLoader and hideLoader)
Expectation: when I click the refresh button I want to load spinner (in background it will refresh the data). Before clicking the button I am able to fetch data using useEffect hook.
//ActionCreators.js
export const EVENT_LOG = "EVENT_LOG";
export const EVENT_FAILURE = "EVENT_FAILURE";
export const SHOW_LOADER = "SHOW_LOADER";
export const HIDE_LOADER = "HIDE_LOADER";
//Actions.js
import {
EVENT_LOG,
EVENT_FAILURE,
SHOW_LOADER,
HIDE_LOADER,
} from "./actionCreators";
import { readList } from "./APIUtilsNew";
export const readLogs = (path) => {
return (dispatch) => {
readList(path)
.then((data) =>
dispatch(
{
type: EVENT_LOG,
payload: data,
},
console.log("EventLog Actions: ", data)
)
)
.catch((error) => {
dispatch({
type: EVENT_FAILURE,
payload: error,
});
throw error;
});
};
};
export const showLoader = () => (dispatch) => {
dispatch({
type: SHOW_LOADER,
});
};
export const hideLoader = () => (dispatch) => {
dispatch({
type: HIDE_LOADER,
});
};
//Reducers.js
import {
EVENT_LOG,
EVENT_FAILURE,
HIDE_LOADER,
SHOW_LOADER,
} from "../../actionCreators/index";
export const initialState = {
loading: false,
eventData: [],
eventError: false,
};
const eventReducer = (state = initialState, action) => {
switch (action.type) {
case EVENT_LOG:
return {
...state,
eventData: action.payload,
};
case EVENT_FAILURE:
return {
...state,
eventError: action.payload,
};
case HIDE_LOADER:
return {
...state,
loading: false,
};
case SHOW_LOADER:
return {
...state,
loading: true,
};
default:
return state;
}
};
export default eventReducer;
//React Component
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { readLogs, showLoader, hideLoader } from "./eventActions";
import { FormattedMessage } from "react-intl";
import { XGrid } from "#material-ui/x-grid";
import { CSVLink } from "react-csv";
import IconBtn from "./IconBtn";
import MaterialTheme from "./MaterialTheme";
import { ThemeProvider as MuiThemeProvider } from "#material-ui/core/styles";
import Refresh from "./Refresh";
export default function EventsLog() {
const dispatch = useDispatch();
const eventLogs = useSelector(
(state) => state.eventReducer.eventData.data || []
);
const show = useSelector((state) => state.eventReducer.loading);
const hide = useSelector((state) => state.eventReducer.loading);
useEffect(() => {
dispatch(readLogs("/events"));
}, [dispatch]);
const update = () => {
dispatch(showLoader());
dispatch(hideLoader());
};
let rows = eventLogs.map((obj, index) => {
return (rows = {
id: index + 1,
Time: obj.time,
dateTime: obj.dateTime,
ID: obj.deviceId
});
});
const columns = [
{
field: "Time",
flex: 1,
type: "dateTime",
renderHeader: () => <FormattedMessage id={"time"} />
},
{
field: "dateTime",
flex: 1,
type: "dateTime",
renderHeader: () => <FormattedMessage id={"dateTime"} />
},
{
field: "ID",
flex: 1,
renderHeader: () => <FormattedMessage id={"id"} />
}
];
return (
<div>
<h1>
<FormattedMessage id="event.eventLog" />
<span>
<IconBtn iconLabel="refresh" />
</span>
<CSVLink data={rows} filename={"Log.csv"}>
<IconBtn iconLabel="cloud_download" onClick={update} />
</CSVLink>
</h1>
<div style={{ height: "90%", width: "100%" }}>
<MuiThemeProvider theme={MaterialTheme}>
<Refresh />
<XGrid
pageSize={50}
rowsPerPageOptions={[25, 50, 100]}
rows={rows}
columns={columns}
pagination={true}
hideFooterSelectedRowCount={true}
/>
</MuiThemeProvider>
</div>
</div>
);
}
This is the component where my spinner resides. I want to fetch this component while loading spinner
//Refresh Component
import React from "react";
export default function Refresh() {
return <div>Spinner....</div>;
}
I saw few examples online, where I found everything is in class components
// component Example
class FullPageLoader extends Component {
state = { }
render() {
const {loading} = this.props;
if(!loading) return null;
return (
<div class="loader-container">
<div className="loader">
<img src={LoaderGif} />
</div>
</div>
);
}
}
const mapStateToProps = state => ({ loading: state.application.loading })
export default connect(mapStateToProps)(FullPageLoader);
// Another Component
updateProfile = () =>{
this.props.dispatch( showLoader() )
Axios.post(`https://jsonplaceholder.typicode.com/users`, { user : { name : 'Test User' } })
.then(res => {
console.log( res );
this.props.dispatch( hideLoader() )
})
/* setTimeout(() => {
this.props.dispatch( hideLoader() )
}, 2000); */
}
<Button bsStyle="info" pullRight fill onClick={this.updateProfile} >
Update Profile
</Button>
Can somebody help me how to convert the above class to functional based component and instead of using mapStateToProps to hooks (or) please tell me how to load the spinner using react-redux hooks. I appreciate the help!
More easier way is to show and hide the loader in the action itself. Before the promise, setLoader as true. And in then and catch you can hide loader.
export const readLogs = (path) => {
return (dispatch) => {
showLoader();
readList(path)
.then((data) => {
hideLoader();
dispatch(
{
type: EVENT_LOG,
payload: data,
},
console.log("EventLog Actions: ", data)
)
})
.catch((error) => {
hideLoader();
dispatch({
type: EVENT_FAILURE,
payload: error,
});
throw error;
});
};
};
if it has to be done in the component itself, You can add a delay rather than calling them immediately. There doesn't seem to be any action that is happening here.
const update = () => {
dispatch(showLoader());
setTimeout(() => {
dispatch(hideLoader());
}, 1000);
};

MERN App: Redux store disappears on refresh for some but not all of my components

ISSUE (edited to add store.js and express routes)
I can access and refresh Tournaments.All component because the state doesn't break on page refresh, but the component that displays a single Tournament breaks when I refresh the page or type the URL directly (it ONLY works when I access it via button/link)
EXPLANATION
My app has two models: Tournament and User.
Each one has two components: Index (shows all) and Show (shows one)
My app state tree looks like:
>tournament
tournaments: [],
showTournament: "",
loading: false
>player
players: [],
showPlayer: "",
loading: false
Tournament Index works. Component renders and displays everything. I can access it via Link or URL and I can refresh the page without breaking anything.
I'll place the code for tourney Index and Show together below (including Loading since that occurs right before the data loads)
Store
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
BackEnd Express Routes
// #route GET /tournaments
// #descrip Get All, INDEX
// #access Public
router.get('/', (req, res) => {
Tournament.find()
.sort({ date: -1 })
.then(tournaments => res.json(tournaments))
.catch(err => console.log(err));
});
// #route SHOW /tournaments/:id
// #descrip Display a single tournament
// #access Public
router.get('/:id', (req, res) => {
Tournament.findById(req.params.id)
.then(tournament => res.json(tournament))
.catch(err => console.log(err));
});
Reducer
const initialState = {
tournaments: [],
showTournament: "",
loading: false
};
export default function(state = initialState, action) {
switch(action.type) {
case GET_TOURNAMENTS:
return {
...state,
tournaments: action.payload,
loading: false
};
case SHOW_TOURNAMENT:
return {
...state,
showTournament: state.tournaments.find(tournament => tournament._id === action.payload),
loading: false
};
case TOURNAMENTS_LOADING:
case TOURNAMENT_LOADING:
return {
...state,
loading: true
}
};
};
Actions
// Tournament Index
export const getTournaments = () => dispatch => {
dispatch({ type: TOURNAMENTS_LOADING });
axios
.get('/tournaments')
.then(res => dispatch({
type: GET_TOURNAMENTS,
payload: res.data
}))
.catch(err => dispatch(returnErrors(err.response.data, err.response.status)));
};
// Tournament Show
export const showTournament = id => dispatch => {
dispatch({ type: TOURNAMENT_LOADING });
axios
.get(`/tournaments/${id}`)
.then(() => dispatch({
type: SHOW_TOURNAMENT,
payload: id
}))
.catch(err => dispatch(returnErrors(err.response.data, err.response.status)));
};
Index Component
import React, { Component, Fragment } from 'react';
import { Link } from 'react-router-dom';
import { Jumbotron, Button } from 'reactstrap';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { InProgress, ResultsPopover } from './buttons';
import { getTournaments, deleteTournament } from '../../actions/tournamentActions';
import TournamentDescription from './descriptions';
import DeleteModal from '../delete/DeleteModal';
class TournamentIndex extends Component {
constructor(props) {
super(props);
this.onDelete = this.onDelete.bind(this);
};
componentDidMount() {
this.props.getTournaments();
};
static propTypes = {
getTournaments: PropTypes.func.isRequired,
tournament: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired
};
onDelete(id) {
this.props.deleteTournament(id);
};
render() {
const { tournaments } = this.props.tournament;
const { isAuthenticated, user } = this.props.auth;
return tournaments.map(({ _id, title, hostedBy, status }) => {
return (
<Jumbotron key={_id} className={title.toLowerCase().replace(/\s+/g, '')}>
<h1 className="mb-5 text-center">
{ title }
<p style={{fontSize: '0.6em'}} className="text-muted">Hosted by: { hostedBy }</p>
</h1>
<h4>
<TournamentDescription key={_id} title={title} />
</h4>
<hr className="my-4"/>
{
isAuthenticated && user.username === hostedBy ?
<Fragment>
<DeleteModal
page={"Tournament Index"}
title={"Delete Tournament"}
onClick={() => this.onDelete(_id)}
/>
</Fragment>
:
null
}
<Link
to={ status === "Open" ? `/tournaments/${_id}` : `/tournaments/${_id}/start` }
className="remove-underline"
>
<Button color="secondary" outline block className="mt-2">
<b className="enter-btn">Enter</b>
</Button>
</Link>
{ status === "Closed" ? <InProgress /> : null }
{ status === "Complete" ? <ResultsPopover /> : null }
</Jumbotron>
);
});
};
};
const mapStateToProps = state => ({
tournament: state.tournament,
auth: state.auth
});
export default connect(mapStateToProps, { getTournaments, deleteTournament })(TournamentIndex);
Show Component
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import TournamentDescription from './descriptions';
import { showTournament, addParticipant, updateTournamentStatus } from '../../actions/tournamentActions';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { TournamentSignUp, StartTournament } from './buttons';
import { Button, Spinner } from 'reactstrap';
class TournamentShow extends Component {
constructor(props) {
super(props);
this.onSignUp = this.onSignUp.bind(this);
this.onStartTournament = this.onStartTournament.bind(this);
};
componentDidMount() {
const id = this.props.match.params.id;
this.props.showTournament(id);
};
static propTypes = {
tournament: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired
};
onSignUp(tournamentId, user) {
this.props.addParticipant(tournamentId, user);
};
onStartTournament(tourneyId, tourneyParticipants) {
this.props.updateTournamentStatus(tourneyId, tourneyParticipants);
};
render() {
const { _id, title, hostedBy, status, participants } = this.props.tournament.showTournament;
const { isAuthenticated, user } = this.props.auth;
return (
<div>
{ this.props.tournament.loading ?
<Spinner color="light" /> :
<div style={{color: "lightgrey"}}>
<h1 className="text-center">
{ title }
<span style={{fontSize: "0.5em"}}> by { hostedBy }</span>
</h1>
<h3>
<TournamentDescription key={_id} title={ title } />
</h3>
<br />
<p className="text-center" style={{color: "#56A8CBFF", fontSize: "2em"}}>
~ { status } for registration ~
</p>
<h4 className="text-left mt-5">
{
participants && participants.length === 1 ?
`${participants && participants.length} Registered Fighter` :
`${participants && participants.length} Registered Fighters`
}
</h4>
<ul>
{
participants && participants.map(participant => (
<li key={participant._id} className="text-left" style={{fontSize: "1.1em"}}>{participant.username}</li>
))
}
</ul>
{
isAuthenticated ?
<div>
<TournamentSignUp
participants={participants}
userId={user._id}
onClick={() => this.onSignUp(_id, user)}
/>
</div> :
<Button block disabled>Log in to sign up for this tournament</Button>
}
{
isAuthenticated && user.username === hostedBy ?
<div>
<StartTournament
participants={participants}
onClick={() => this.onStartTournament(_id, participants)}
/>
</div> :
null
}
</div>
}
<br /><Link to="/">Back to Tournaments main page</Link>
</div>
)
}
};
const mapStateToProps = state => ({
tournament: state.tournament,
auth: state.auth
});
export default connect(mapStateToProps, { showTournament, addParticipant, updateTournamentStatus })(TournamentShow);
SUMMARY
Index works. I can view all tournaments, refresh the page and access it via URL.
Show does not. I can only access it within the app, but it breaks if I type the URL or refresh the page.
State Tree when I access Show from within the App:
tournament
tournaments: [...],
showTournament: {...},
loading: false
State Tree when I type the URL or refresh the page:
tournament
tournaments: [],
loading: false
It clears tournaments completely, and actually removes showTournament
One solution I found was to implement a loading spinner, which I did, but that didn't change the State behavior.
Thanks!

setState into store component

I have to set a value on from a API into a newly created <button> component handled by Redux, but I don't know if I can use setState for this. I created a reducer and an action SET_VOTE_COUNT but I'm not seeing how this is done. This is my first Redux project, so here is the code:
// ./src/js/components/CounterList.js
import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement } from '../actions/reducer';
import Counter from './Counter';
const CounterList = ({
counters,
onIncrement,
onDecrement
}) => (
<ul>
{counters.map(counter =>
<Counter style={{div: "voting"}}
key={counter.id}
value={counter.count}
onIncrement={() => onIncrement(counter.id)}
onDecrement={() => onDecrement(counter.id)}
/>
)}
</ul>
);
const mapStateToProps = (state) => {
return {
counters: state
};
};
const mapDispatchToProps = (dispatch) => {
return {
onIncrement: (id) => dispatch(increment(id)),
onDecrement: (id) => dispatch(decrement(id))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(CounterList);
// ./src/js/components/Counter.js
import React, { Component } from 'react';
class Counter extends Component {
render() {
return (
<div className="voting">
<span>{this.props.value}</span>
<button
onClick={() => this.props.onIncrement()}>
+
</button>
<button
onClick={() => this.props.onDecrement()}>
-
</button>
</div>
);
}
}
export default Counter;
import React, {Component} from 'react';
import logo from '../../logo.svg';
import '../../App.css';
import AddButton from './AddButton'
class Posts extends Component {
constructor(props) {
super(props);
this.state = {
response: ''
};
}
componentDidMount() {
fetch(
"/posts"
).then(response => response.json())
.then(data => this.setState({ response: data }))
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{Array.isArray(this.state.response) &&
this.state.response.map(resIndex => <>
{ resIndex.voteScore}
<AddButton className="voting"/>
<p> { resIndex.title }, by { resIndex.author } </p>
<p> { resIndex.body } </p>
<p> {resIndex.category} </p>
</>
)}
</header>
</div>
)
}
}
export default Posts;
import React from 'react';
import { add_counter, setVoteCount } from '../actions/reducer';
import { connect } from 'react-redux';
const AddButton = ({dispatch}) => (
<div className="voting">
<button
onClick={() => {
dispatch(setVoteCount())
// dispatch(add_counter());
}}>
Vote
</button>
</div>
);
export default connect()(AddButton);
The reducer:
// ./src/js/actions/counters.js
export const setVoteCount = (id) => {
return {
type: "SET_VOTE_COUNT",
id
};
}
export const increment = (id) => {
return {
type: "INCREMENT",
id
};
};
export const decrement = (id) => {
return {
type: "DECREMENT",
id
};
};
export const add_counter = () => {
return {
type: "ADD_COUNTER"
};
};
store action:
import { createStore, applyMiddleware, compose } from 'redux';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const change_counter = (state = {}, action) => {
switch (action.type) {
case "SET_VOTE_COUNT":
if (state.id !== action.id) {
return state;
}
return {
...state,
count : 37
}
case "INCREMENT":
if (state.id !== action.id) {
return state;
}
return {
...state,
count: state.count+1
};
case "DECREMENT":
if (state.id !== action.id) {
return state;
}
return {
...state,
count: state.count - 1
};
default:
return state;
}
};
let nextId = 0;
const counters = (state = [], action) => {
switch (action.type) {
case "ADD_COUNTER":
return [...state, {id: nextId++, count: 0}];
case "SET_VOTE_COUNT":
return [...state, {id: nextId++, count: action.count}];
case "INCREMENT":
return state.map(counter => change_counter(counter, action));
case "DECREMENT":
return state.map(counter => change_counter(counter, action));
default:
return state;
}
}
export default createStore(counters, composeEnhancers(applyMiddleware()));
I can upload it to GitHub if necessary. Many thanks.
In the AddButton component,the actions should be wrapped in mapDispatchToProps and passed to the connect function. You are calling the raw action in your example, but you need to wrap it with dispatch for it to update the store.
However, I'm not sure what you are trying to update the store with exactly. The action payload is empty in your example, and the reducer has 37 hardcoded as the state.count in response the SET_VOTE_COUNT action type. Did you mean to pass something from the API response?
<AddButton count={resIndex.count} className="voting"/>
import React from 'react';
import { add_counter, setVoteCount } from '../actions/reducer';
import { connect } from 'react-redux';
const mapDispatchToProps = {
setVoteCount
};
const AddButton = props => (
<div className="voting">
<button onClick={() => {
props.setVoteCount(props.count);
}}>
Vote
</button>
</div>
);
export default connect(null, mapDispatchToProps)(AddButton);

Resources