I have been trying to persist my redux store through a reload. I was using useEffect to dispatch my actions at first but then when I tried to reload the page router became undefined and I got a 500 error. After that I tried using getInitialProps and use the ctx.query.id but I ran into another error saying that hooks can only be called inside of the body of a function component.
How do I make it so hooks work inside of getInitialProps and what is the best way of persisting my store data through a reload?
export default function CarPage() {
const dispatch = useDispatch()
const router = useRouter()
const car = useSelector((state) => state.cars.car)
/*
useEffect(() => {
if(!car && router) {
dispatch(getCar(router.query.id))
}
}, [])
*/
return (
<Container>
<h2>{car.model}</h2>
</Container>
)
}
CarPage.getInitialProps = async (ctx) => {
const dispatch = useDispatch()
dispatch(getCar(ctx.query.id))
}
To persist redux store through a page reload, we definitely need to use browser storage.
I suggest using https://github.com/rt2zz/redux-persist.
To use dispatch inside getInitialProps, please try with this code snippet instead of using useDispatch() hook.
CarPage.getInitialProps = async ({ store, query }) => {
store.dispatch(getCar(query.id));
return { initialState: store.getState() };
}
Related
The problem:
getServerSideProps is blocking the whole site on subsequent requests, even when only the props are requested and all other js is already loaded.
So I was wondering if it is possible to add a loading component to each page (something like the dynamic layout: https://github.com/vercel/next.js/tree/canary/examples/layout-component)
and show it instantly while waiting till the props are loaded.
I know about the Router from nextjs and the events but how would I show the loader when I got only the url on routeChangeStart?
I use getInitialProps now. Thought it was deprecated, but it's not.
I can now do something like this:
const MyPage = (props) => {
const [data, setData] = useState(null)
useEffect(() => {
if (props.data) {
setData(props.data)
} else {
// fetch client side and setData
}
});
return (
<>
{data === null && <LoadingMyPage/>}
{data && <TheActualContent/>}
</>
);
}
MyPage.getInitialProps = async () => {
if (window) return {data: undefined};
// fetch server side
return {data: ...}
}
I am new to nextjs. I want to prerender a page on server and want to delay rendering till API call is resolved. to achieve this i am using getStaticProps as mentioned in nextjs official docs.
here is the position of the file in my code structure:-
i am exporting my getStaticProps from index.js
here is the code snippet :-
export const getStaticProps = async () => {
const res = await axios.get(`http://blogexample.com/blog/posts`);
const blogList = await res.data
return {
props: {
blogList
}
}
}
const Blog = (props) => {
const { blogList } = props;
useEffect(() => {
console.log('list',blogList)
},[blogList])
return(
....
);
}
export default Blog;
problem is that, in browser console....my console.log('list',blogList) statement prints undefined
what i am doing wrong
getStaticProps is executed during build time. Use getServerSideProps
instead.
I have a Redux app and it is working perfectly without any errors. Now I am trying to test it with Enzyme, Jest and Sinon:
it('calls constructor', () => {
sinon.spy(SavedVariantsComponent.prototype, 'constructor')
const store = configureStore()(STATE1)
wrapper = mount(<SavedVariantsComponent store={store} match={{ params: {} }} />)
expect(SavedVariantsComponent.prototype.constructor).toHaveProperty('callCount', 1)
})
In SavedVariantsComponent I have mapDispatchToProps:
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onSubmit: (updates) => {
dispatch(updateSavedVariantTable(updates))
const { match, analysisGroup } = ownProps
const { familyGuid, variantGuid, tagArray, gene } = match.params
const familyGuids = familyGuid ? [familyGuid] : (analysisGroup || {}).familyGuids
const combineVariants = /combined_variants/.test(match.url)
dispatch(loadSavedVariants(combineVariants, familyGuids, variantGuid, tagArray, gene))
},
loadSavedVariants: (...args) => dispatch(loadSavedVariants(...args)),
}
}
And loadSavedVariants look like that:
export const loadSavedVariants = (combineVariants, familyGuids, variantGuid, tagArray, gene = '') => {
return (dispatch, getState) => {
...
...
and the error while running jest is:
Actions must be plain objects. Use custom middleware for async actions.
Which makes an HTTP Request that may not work in the current case. How to fix this error? I need to test that the constructor was called, but later on will also need to see how the inner Components are rendered, so need to have mount there. I suppose I am doing something wrong in testing and not in the real code since the latter is working without any errors, warnings or issues.
You probably need to configure your mock store to work with redux-thunk. See: https://github.com/dmitry-zaets/redux-mock-store#asynchronous-actions
import configureStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const middlewares = [thunk] // add your middlewares like `redux-thunk`
const mockStore = configureStore(middlewares)
I'm trying to link up React Apollo with Redux so Apollo performs the queries and mutations, and the returned data is dispatched to the Redux store in order to distribute the data around the app.
I believe I'm close to getting it right, but for some reason the app goes into an infinite loop of Redux dispatches, and I can't figure out why.
See code below:
class Admin extends Component {
constructor(props) {
super(props);
}
render({
adminAllTokens
}, {}) {
return ( /* JSX */ )
);
}
}
const AllRefreshTokens = gql `
query {
allUsers {
refreshToken
email
}
}
`;
const gqlWrapper = graphql(AllRefreshTokens, {
props: ({
ownProps,
data
}) => {
ownProps.receivedAdminTokens(data.allUsers); //dispatch to Redux store
return {
...data,
gqladminAllTokens
};
}
});
function mapStateToProps(state, ownProps) {
return {
adminAllTokens: state.auth.adminAllTokens
};
}
function mapDispatchToProps(dispatch) {
return {
receivedAdminTokens: tokens => {
dispatch(adminTokensReceived(tokens));
}
};
}
const reduxWrapper = connect(mapStateToProps, mapDispatchToProps);
export default compose(reduxWrapper, gqlWrapper)(Admin);
The adminTokensReceived() action is in the reducer file:
export const adminTokensReceived = tokens => ({
type: 'ADMIN_TOKENS_RECEIVED',
tokens
});
The GraphQL query only sends one network request, but the console is showing the ADMIN_TOKENS_RECEIVED action dispatching constantly and crashes the browser.
Thanks in advance
Whenever the Apollo HOC receives new props, it causes your action to fire, which updates the store and sends new props to your Apollo HOC, which causes your action to fire...
There's a couple of different ways you could handle this. In my mind, the most straightforward would be to drop the graphql HOC and use withApollo instead. Something like:
compose(
withApollo,
connect(mapStateToProps, mapDispatchToProps)
lifecycle({
componentDidMount() {
const { client } = this.props
client.query({ query: AllRefreshTokens })
.then(({data}) => {
receivedAdminTokens(data.allUsers)
})
.catch( //any error handling logic )
}
})
)
The above uses recompose's lifecycle but you could just as easily stick the componentDidMount method inside your component.
That said, it seems a little redundant to use Redux to store the results of your GraphQL queries when Apollo already does it for you.
Apollo's default behavior is to retrieve the data from the cache first, and only make a network request if the data doesn't exist (which is also why you only saw the one network call). That means any number of components inside your app could be wrapped with the same graphql HOC, and only the first component to be rendered would trigger a request to your GraphQL endpoint -- all other components would get their data from the cache.
I try to implement a async react-select (Select.Async). The problem is, we want to do the fetch in redux-saga. So if a user types something, the fetch-action should be triggered. Saga then fetches the record and saved them to the store. This works so far.
Unfortunately loadOptions has to return a promise or the callback should be called. Since the newly retrieved options get propagated with a changing property, I see no way to use Select.Async together with saga to do the async fetch call. Any suggestions?
<Select.Async
multi={false}
value={this.props.value}
onChange={this.onChange}
loadOptions={(searchTerm) => this.props.options.load(searchTerm)}
/>
I had a hack where i assigned the callback to a class variable and resolve it on componentWillReceiveProps. That way ugly and did not work properly so i look for a better solution.
Thanks
redux-saga is for handling side effects like asynchronously receiving options for react-select. That's why you should leave the async stuff to redux-saga. I have never used react-select but by just looking at the documentation I would solve it this way:
Your component gets very simple. Just get value and options from your redux store. optionsRequested is an action creator for the OPTIONS_REQUESTED action:
const ConnectedSelect = ({ value, options, optionsRequested }) => (
<Select
value={value}
options={options}
onInputChange={optionsRequested}
/>
)
export default connect(store => ({
value: selectors.getValue(store),
options: selectors.getOptions(store),
}), {
optionsRequested: actions.optionsRequested,
})(ConnectedSelect)
A saga definition watches for OPTIONS_REQUESTED action that is trigged by onInputChange, loads the data with given searchTerm from server and dispatches OPTIONS_RECEIVED action to update redux store.
function* watchLoadOptions(searchTerm) {
const options = yield call(api.getOptions, searchTerm)
yield put(optionsReceived(options))
}
In other words: Make your Component as pure as possible and handle all side-effect/async calls in redux-saga
I hope this answer was useful for you.
The main idea is that you are capable to dispatch redux actions using application context from
import React from 'react';
import { connect } from 'react-redux';
import Select from '#components/Control/Form/Skin/Default/Select';
import { reduxGetter, reduxSetter, required as req } from '#helpers/form';
import { companyGetTrucksInit } from "#reduxActions/company";
import AppContext from '#app/AppContext';
const FIELD_NAME = 'truck';
export const getReduxValue = reduxGetter(FIELD_NAME);
export const setReduxValue = reduxSetter(FIELD_NAME);
const SelectCompanyTruck = (props) => {
const {
required,
validate=[]
} = props;
const vRules = [...validate];
if (required)
vRules.push(req);
return (
<AppContext.Consumer>
{({ dispatchAction }) => (
<Select
loadOptions={(inputValue, callback) => {
function handleResponse(response) {
const { data: { items } } = response;
const options = items.map(i => ({ label: i.name, value: i.id }));
callback(options);
}
dispatchAction(companyGetTrucksInit, { resolve: handleResponse, inputValue });
}}
name={FIELD_NAME}
{...props}
/>
)}
</AppContext.Consumer>
);
}
export default SelectCompanyTruck;