Currently have an issue where by I want to update props based on 'componentdidupdate'. However everytime i call this function (onUpdateSelectedDate), it keeps saying
onUpdateSelectedDate is not defined
I have tried the following:
onUpdateSelectedDate(toggledDate)
this.onUpdateSelectedDate(toggledDate)
this.props.onUpdateSelectedDate(toggledDate)
and still unsure why i am getting this error.
Code below
import DayPicker from "react-day-picker"
import React, {Component} from 'react'
import './calendarDatePicker.scss'
import propTypes from 'prop-types'
import { connect } from 'react-redux'
class CalendarDatePicker extends Component {
state = {
toggledDate: null,
}
componentDidUpdate = () => {
const toggledDate = this.state.toggledDate
onUpdateSelectedDate(toggledDate)
}
render() {
const selectedDate = this.props.selectedDays
const onDayClick = this.props.onDayClick
const toggledDate = this.state.toggledDate
const modifiers = {
}
return (
<DayPicker
selectedDays={toggledDate===null ? selectedDate : toggledDate}
onDayClick={onDayClick}
todayButton="Go to Today"
firstDayOfWeek={1}
modifiers = {modifiers}
onMonthChange={(d) => this.setState({toggledDate: d})}
/>
)
}
}
CalendarDatePicker.propTypes = {
selectedDays: propTypes.instanceOf(Date),
onDayClick: propTypes.func,
onUpdateSelectedDate: propTypes.func,
}
const mapStateToProps = (state) => {
return {
//toggledDate: state.diaryContext.activities.selectedDates,
}
}
const mapDispatchToProps = (dispatch) => {
return {
onUpdateSelectedDate: (toggledDate) => { dispatch(diaryActions.updateSelectedDate(toggledDate)) },
}
}
export default connect(null, mapDispatchToProps)(CalendarDatePicker)
You use a wrong signature for the componentDidUpdate method it should be componentDidUpdate(prevProps, prevState) and then you can access your function from mapStateToProps like that:
componentDidUpdate (prevProps, prevState) {
const toggledDate = prevState.toggledDate
prevProps.onUpdateSelectedDate(toggledDate)
}
Related
I am a newbie in React and Next JS, I want to set initial auth user data on initial load from the __app.js. But using dispatch throwing error "Invalid hook call". I know according to docs calling hooks in render function is wrong. but I am looking for an alternate solution to this.
How I can set auth data one-time so that will be available for all the pages and components.
I am including my code below.
/contexts/app.js
import { useReducer, useContext, createContext } from 'react'
const AppStateContext = createContext()
const AppDispatchContext = createContext()
const reducer = (state, action) => {
switch (action.type) {
case 'SET_AUTH': {
return state = action.payload
}
default: {
throw new Error(`Unknown action: ${action.type}`)
}
}
}
export const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, {})
return (
<AppDispatchContext.Provider value={dispatch}>
<AppStateContext.Provider value={state}>
{children}
</AppStateContext.Provider>
</AppDispatchContext.Provider>
)
}
export const useAuth = () => useContext(AppStateContext)
export const useDispatchAuth = () => useContext(AppDispatchContext)
/_app.js
import 'bootstrap/dist/css/bootstrap.min.css'
import '../styles/globals.css'
import App from 'next/app'
import Layout from '../components/Layout'
import { mutate } from 'swr'
import { getUser } from '../requests/userApi'
import { AppProvider, useDispatchAuth } from '../contexts/app'
class MyApp extends App {
render() {
const dispatchAuth = useDispatchAuth()
const { Component, pageProps, props } = this.props
// Set initial user data
const setInitialUserData = async () => {
if (props.isServer) {
const initialData = {
loading: false,
loggedIn: (props.user) ? true : false,
user: props.user
}
const auth = await mutate('api-user', initialData, false)
dispatchAuth({
type: 'SET_AUTH',
payload: auth
})
}
}
//----------------------
// Set initial user data
setInitialUserData()
//----------------------
return (
<AppProvider>
<Layout>
<Component {...pageProps} />
</Layout>
</AppProvider>
)
}
}
MyApp.getInitialProps = async (appContext) => {
let isServer = (appContext.ctx.req) ? true : false
let user = null
let userTypes = {}
// Get user server side
if (isServer) {
await getUser()
.then(response => {
let data = response.data
if (data.status == true) {
// Set user
user = data.data.user
userTypes = data.data.user_types
//---------
}
})
.catch(error => {
//
})
}
//---------------------
return {
props: {
user,
userTypes,
isServer
}
}
}
export default MyApp
I believe this is the intended use of the useEffect hook with an empty array as its second argument:
https://reactjs.org/docs/hooks-effect.html
import {useEffect} from 'react'
class MyApp extends App {
useEffect(()=> {
setInitialUserData()
},[])
render() {
...
}
}
The common cause for my issue when researching this is mutating the state and not returning a new object of the state which causes redux to not recognize a change. However, this is not and has never been an issue and i'm well aware of it. I'm returning a new object. In the logger which you can see in the attached image it displays the successful api call resolved and the nextState is updated but never rendered. Refreshing the page acts exactly the same even though i expected to possibly need to do so upon initial landing to root page.
Component:
import pokemonReducer from '../../reducers/pokemon_reducer';
import PokemonIndexItem from './pokemon_index_item';
import {Route} from 'react-router-dom';
import PokemonDetailContainer from './pokemon_detail_container';
class PokemonIndex extends React.Component {
componentDidMount() {
this.props.requestAllPokemon();
}
render() {
const pokemon = this.props.pokemon;
return (
<section className="pokedex">
<Route path='/pokemon/:pokemonID' component={PokemonDetailContainer} />
<ul>{pokemon && pokemon.map(poke => <li>{poke.name}{poke.id}</li>)}</ul>
</section>
);
}
}
export default PokemonIndex;
and the container:
import {connect} from 'react-redux';
import { selectAllPokemon } from '../../reducers/selectors';
import PokemonIndex from './pokemon_index';
import { requestAllPokemon } from '../../actions/pokemon_actions';
const mapStateToProps = state => ({
pokemon: selectAllPokemon(state)
});
const mapDispatchToProps = dispatch => ({
requestAllPokemon: () => dispatch(requestAllPokemon())
});
export default connect(mapStateToProps, mapDispatchToProps)(PokemonIndex);
the reducer:
import { RECEIVE_ALL_POKEMON, RECEIVE_SINGLE_POKEMON} from '../actions/pokemon_actions';
const pokemonReducer = (initialState = {}, action) => {
Object.freeze(initialState);
switch(action.type) {
case RECEIVE_ALL_POKEMON:
return Object.assign({}, initialState, action.pokemon);
case RECEIVE_SINGLE_POKEMON:
let poke = action.payload.pokemon
return Object.assign({}, initialState, {[poke.id]: poke})
default:
return initialState;
}
};
export default pokemonReducer;
secondary reducer:
import { combineReducers } from 'redux';
import pokemonReducer from './pokemon_reducer'
const entitiesReducer = combineReducers({
pokemon: pokemonReducer,
});
export default entitiesReducer;
rootreducer:
import {combineReducers} from 'redux';
import entitiesReducer from './entities_reducer';
const rootReducer = combineReducers({
entities: entitiesReducer
});
export default rootReducer;
as requested here is the selectors defined in reducers folder
export const selectAllPokemon = (state) => {
Object.values(state.entities.pokemon);
};
export const selectSinglePokemon = (state) => {
Object.values(state.entities.pokemon)
};
and here is the actions created:
export const RECEIVE_ALL_POKEMON = "RECEIVE_ALL_POKEMON";
export const RECEIVE_SINGLE_POKEMON = "RECEIVE_SINGLE_POKEMON";
import * as APIUtil from '../util/api_util';
export const receiveAllPokemon = (pokemon) => (
{
type: RECEIVE_ALL_POKEMON,
pokemon
}
);
export const requestAllPokemon = () => (dispatch) => {
APIUtil.fetchAllPokemon()
.then(
pokemon =>
{ dispatch(receiveAllPokemon(pokemon));}
);
};
export const receiveSinglePokemon = data => (
{
type: RECEIVE_SINGLE_POKEMON,
data
}
);
export const requestSinglePokemon = id => (dispatch) => {
APIUtil.fetchSinglePokemon(id)
.then(pokemon => {dispatch(receiveSinglePokemon(pokemon));
return pokemon;});
};
nextstate showing in console
As you stated in your question, your redux state is getting properly set but your new state is never being rendered and I think this has to do with your selector. It looks to me that you forgot to return your computed state.
export const selectAllPokemon = (state) => {
Object.values(state.entities.pokemon);
};
// will return undefined
For returning your state you have two options:
Explicit return
export const selectAllPokemon = (state) => {
return Object.values(state.entities.pokemon);
};
Implicit return
export const selectAllPokemon = (state) => (
Object.values(state.entities.pokemon);
);
I refer to this article or look at the examples I created in playground to get a better unstanding of implicit and explicit return in arrow functions.
I'm trying to make a timer in my App with React + Redux.
So I have a component parent:
import React, { Component } from "react";
import { connect } from "react-redux";
import { compose } from "redux";
import QuestionCounter from "../question-counter";
import FinishButton from "../finish-button";
import TimeCounter from "../time-counter";
import PauseButton from "../pause-button";
import testFinished from "../../actions/test-finished";
import timerTick from "../../actions/timer-tick";
import setTimer from "../../actions/set-timer";
import totalWithEwStruct from "../hoc/total-with-ew-structure";
import withIndicators from "../hoc/with-indicators";
const Total = ({ total, testFinished }) => {
const { finishedCount, totalCount, isPaussed, timeLeft } = total;
return (
<div className="test-total">
<QuestionCounter
finishedCount={finishedCount}
totalCount={totalCount}
testFinished={testFinished}
/>
<FinishButton testFinished={testFinished} />
<TimeCounter
timeLeft={timeLeft}
testFinished={testFinished}
setTimer={setTimer}
timerTick={timerTick}
/>
<PauseButton isPaussed={isPaussed} />
</div>
);
};
const mapStateToProps = ({ total, loading, error }) => {
return { total, loading, error };
};
const mapDispatchToProps = {
testFinished,
setTimer,
timerTick
}
export default compose(
totalWithEwStruct(),
connect(mapStateToProps, mapDispatchToProps),
withIndicators()
)(Total);
I try use timerTick by timer in componentDidMount
import React, { Component } from "react";
export default class TimeCounter extends Component {
componentDidMount() {
const { setTimer, timerTick } = this.props;
let timer = setInterval(() => {
timerTick();
console.log("tick");
}, 1000);
setTimer(timer);
}
componentDidUpdate() {
const { timeLeft, testFinished } = this.props;
if (timeLeft <= 0) {
testFinished();
}
}
render() {
const { timeLeft } = this.props;
return (
<div className="question-counter__timeleft">
Времени осталось
<span className="question-counter__timer">{timeLeft}</span>
</div>
);
}
}
So I see "tick" - "tick" - "tick" in console, but React doesn't dispatch my timerTick() function to reducer.
I have tried log to console action.type for debugging, and there is no action of timerTick.
const timerTick = () => {
return {
type: "TIMER_TICK"
};
};
export default timerTick;
Its code of action.
I don't understand why it doesn't work.
Your Total component needs to take timerTick function from props which is the one that is linked with redux store as you have added it to mapDispatchToProps.
If you do not destructure it from props, the ccomponent will use the imported function which isn't an action created unless its passed to dispatch function
const Total = ({ total, testFinished }) => {
const { finishedCount, totalCount, isPaussed, timeLeft, timerTick } = total;
return (
<div className="test-total">
<QuestionCounter
finishedCount={finishedCount}
totalCount={totalCount}
testFinished={testFinished}
/>
<FinishButton testFinished={testFinished} />
<TimeCounter
timeLeft={timeLeft}
testFinished={testFinished}
setTimer={setTimer}
timerTick={timerTick}
/>
<PauseButton isPaussed={isPaussed} />
</div>
);
};
You need to add dispatch of timer tick inside timer tick component. Because child component not aware about the actions.
Please refer below link for more details:
https://itnext.io/dispatching-actions-from-child-components-bd292a51f176
Response
if your component is not connected to redux you won’t be able to dispatch any action.
What do I mean?
Example
import React from “react”;
import { connect } from “react-redux”;
class MyCom extensa React.Component {
componentDidMount () {
const { action } = this.props;
action();
}
render () {
.....
}
}
const toState = state => ({....});
const toDispatch = {
action
};
export default connect(toState, toDispatch)(MyCom);
Explains
Basically connect from ”react-redux” is a HOC a high order component that on javascript world: is none but a high order function. a function that return another function.
I am trying to develop a custom hook which seems to be pretty easy but I am getting an error
Uncaught Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
This is my hook:
import React, { useState, useEffect } from 'react';
const useInfiniteScroll = (isLastPage: boolean, fetchFn: any) => {
const [pageCount, setPageCount] = useState(0);
const triggerFetchEvents = (): void => {
let response;
setPageCount(() => {
if (!isLastPage) {
response = fetchFn(pageCount + 1, 5, 'latest');
}
return pageCount + 1;
});
return response;
};
useEffect(() => {
triggerFetchEvents();
}, []);
return pageCount;
};
export default useInfiniteScroll;
And the component here I am calling it:
import React, { FC } from 'react';
import { connect } from 'react-redux';
import { fetchEvents } from '../../shared/actions/eventActions';
import { AppState } from '../../shared/types/genericTypes';
import EventModel from '../../shared/models/Event.model';
import EventListPage from '../../components/events/EventListPage';
import useInfiniteScroll from '../../shared/services/triggerInfiniteScroll';
type Props = {
fetchEvents?: any;
isLastPage: boolean;
eventsList?: EventModel[];
};
const mapState: any = (state: AppState, props: Props): Props => ({
eventsList: state.eventReducers.eventsList,
isLastPage: state.eventReducers.isLastPage,
...props
});
const actionCreators = {
fetchEvents
};
export const EventsScene: FC<Props> = props => {
const { eventsList, fetchEvents, isLastPage } = props;
const useIn = () => useInfiniteScroll(isLastPage, fetchEvents);
useIn();
// const [pageCount, setPageCount] = useState(0);
// const triggerFetchEvents = (): void => {
// let response;
// setPageCount(() => {
// if (!isLastPage) {
// response = fetchEvents(pageCount + 1, 1, 'latest');
// }
// return pageCount + 1;
// });
// return response;
// };
// useEffect(() => {
// triggerFetchEvents();
// }, []);
if (!eventsList || !eventsList.length) return null;
return (
<EventListPage
eventsList={eventsList}
isLastPage={isLastPage}
triggerFetchEvents={useIn}
/>
);
};
export default connect(
mapState,
actionCreators
)(EventsScene);
I left the commented code there to show you that if I uncomment the code and remove useInfiniteScroll then it works properly.
What could I be missing?
UPDATE:
This is EventListPage component
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import EventModel from '../../shared/models/Event.model';
import { formatDate } from '../../shared/services/date';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Card from 'react-bootstrap/Card';
type Props = {
eventsList?: EventModel[];
isLastPage: boolean;
triggerFetchEvents: any;
};
export const EventListPage: React.FC<Props> = props => {
const { eventsList, triggerFetchEvents, isLastPage } = props;
const [isFetching, setIsFetching] = useState(false);
const fetchMoreEvents = (): Promise<void> =>
triggerFetchEvents().then(() => {
setIsFetching(false);
});
const handleScroll = (): void => {
if (
document.documentElement.offsetHeight -
(window.innerHeight + document.documentElement.scrollTop) >
1 ||
isFetching
) {
return;
}
return setIsFetching(true);
};
useEffect(() => {
if (isFetching) return;
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
useEffect(() => {
if (!isFetching) return;
if (!isLastPage) fetchMoreEvents();
}, [isFetching]);
if (!eventsList) return null;
return (
<Container className='article-list mt-5'>
///...
</Container>
);
};
export default EventListPage;
In EventsScene, change useInfiniteScroll to be invoked directly at the function body top-level (not sure why you are creating this indirection in the first place):
// before
const useIn = () => useInfiniteScroll(isLastPage, fetchEvents);
useIn();
// after
useInfiniteScroll(isLastPage, fetchEvents)
React expects Hook calls to only happen at the top-level as it relies on the order of Hooks to be always the same. If you wrap the Hook in a function, you can potentially invoke this function in many code locations disturbing the Hooks' order.
There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state. Link
I have a route to a component HandlingIndex:
<Route strict path={handlingCasePath} component={HandlingIndex} />
HandlingIndex is wrapped with a trackRouteParam component. trackRouteParam component looks like this:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { parseQueryString } from '../../utils/urlUtils';
const defaultConfig = {
paramName: '',
parse: a => a,
paramPropType: PropTypes.any,
storeParam: () => undefined,
getParamFromStore: () => undefined,
isQueryParam: false,
paramsAreEqual: (paramFromUrl, paramFromStore) => paramFromUrl === paramFromStore
};
/**
* trackRouteParam
*
* Higher order component that tracks a route parameter and stores in the application
* state whenever it changes.
* #param config
*/
const trackRouteParam = config => (WrappedComponent) => {
class RouteParamTrackerImpl extends Component {
constructor() {
super();
this.updateParam = this.updateParam.bind(this);
}
componentDidMount() {
this.updateParam();
}
componentDidUpdate(prevProps) {
this.updateParam(prevProps.paramFromUrl);
}
componentWillUnmount() {
const { storeParam } = this.props;
storeParam(undefined);
}
updateParam(prevParamFromUrl) {
const { paramFromUrl, storeParam, paramsAreEqual } = this.props;
if (!paramsAreEqual(paramFromUrl, prevParamFromUrl)) {
storeParam(paramFromUrl);
}
}
render() {
const {
paramFromUrl,
paramFromStore,
storeParam,
paramsAreEqual,
...otherProps
} = this.props;
return <WrappedComponent {...otherProps} />;
}
}
const trackingConfig = { ...defaultConfig, ...config };
RouteParamTrackerImpl.propTypes = {
paramFromUrl: trackingConfig.paramPropType,
paramFromStore: trackingConfig.paramPropType,
storeParam: PropTypes.func.isRequired,
paramsAreEqual: PropTypes.func.isRequired
};
RouteParamTrackerImpl.defaultProps = {
paramFromUrl: undefined,
paramFromStore: undefined
};
const mapStateToProps = state => ({ paramFromStore: trackingConfig.getParamFromStore(state) });
const mapDispatchToProps = dispatch => bindActionCreators({ storeParam: trackingConfig.storeParam }, dispatch);
const mapMatchToParam = (match, location) => {
const params = trackingConfig.isQueryParam ? parseQueryString(location.search) : match.params;
return trackingConfig.parse(params[trackingConfig.paramName]);
};
const mergeProps = (stateProps, dispatchProps, ownProps) => ({
...ownProps,
...stateProps,
...dispatchProps,
paramFromUrl: mapMatchToParam(ownProps.match, ownProps.location),
paramsAreEqual: trackingConfig.paramsAreEqual
});
const RouteParamTracker = withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(RouteParamTrackerImpl));
RouteParamTracker.WrappedComponent = WrappedComponent;
Object.keys(RouteParamTracker).forEach((ownPropKey) => {
RouteParamTracker[ownPropKey] = WrappedComponent[ownPropKey];
});
return RouteParamTracker;
};
export default trackRouteParam;
In the component HandlingIndex, I am trying to get a param caseNumber from the url. Just showing the relevant parts here from the component:
const mapStateToProps = state => ({
selectedCaseNumber: getSelectedCaseNumber(state)
});
export default trackRouteParam({
paramName: 'caseNumber',
parse: caseNumberFromUrl => Number.parseInt(caseNumberFromUrl , 10),
paramPropType: PropTypes.number,
storeParam: setSelectedCaseNumber,
getParamFromStore: getSelectedCaseNumber
})(connect(mapStateToProps)(requireProps(['selectedCaseNumber'])(HandlingIndex)));
Action creator for the setSelectedCaseNumber is:
export const setSelectedCaseNumber= caseNumber=> ({
type: SET_SELECTED_CASE_NUMBER,
data: caseNumber
});
So, when I am going to the route 'case/1234', where the parameter is caseNumber: 1234 where I am setting the selectedCaseNumber I see that the data field is NaN. On inspecting the console, I can see that I in the function:
const mapMatchToParam = (match, location) => {
const params = trackingConfig.isQueryParam ? parseQueryString(location.search) : match.params;
return trackingConfig.parse(params[trackingConfig.paramName]);
};
I can see that match.params is an empty object.
I am not sure why is that, why I am getting an empty object?
In trackRouteParam HOC,
At line:
const RouteParamTracker = withRouter(connect(mapStateToProps, mapDispatchToProps, mergeProps)(RouteParamTrackerImpl));
You try edit:
const RouteParamTracker = connect(mapStateToProps, mapDispatchToProps, mergeProps)(withRouter(RouteParamTrackerImpl));
Hope can help you!