I am pretty new with NextJS (12) and I am creating an ecommerce website in which I have some UI filters; I am using getServerSideProps to fetch data since I need the site to be SEO compliant.
When a user clicks on the filters, I am updating the global state and triggering next/router that fetches fresh data and populate the page simply passing query params to the url. Everything seems to work pretty well.
When a user lands on the site with active filters (via url query params), I am populating the page with correct data via getServerSideProps (parsing url query params), but now I should also enrich Redux state with the active filters, to show the correct UI.
Is there any way to initialize Redux Toolkit store via url params on page load (SSR)?
I know I am not posting any code here, but I need to know if using Redux Toolkit in this scenario could be overkill in the first place, and if there's any way to achieve what I need without over complicated sync libraries.
Thank you very much
I don't know if this could help you and even if this could be a good practice or an anti-pattern instead, but - since I suppose that you've passed your store to the app there - why don't you just grab your query params via useRouter in the _app, compose the actual store in there with query string and then pass it to the store provider?
import { AppProps } from 'next/app';
import { useRouter } from 'next/router';
import { Provider as StoreProvider } from 'react-redux';
import store from '../store';
const App = ({ Component, pageProps }: AppProps) => {
const { query } = useRouter();
const realStore = doSomethingWithQueryAndReturnRealStore(store, query);
return (
<StoreProvider store={realStore}>
<Component {...pageProps} />
</StoreProvider>
);
};
Keep in mind that I haven't tested it, it's just an assumption :)
Related
I'm having a lot of trouble learning to properly load data into state in my todo app.
I have a next.js page component pages/index.tsx where I load data from my API via getServerSideProps and return it as a page prop called tasksData.
The tasksData is being returned properly and I can access them in my page component just fine via prop destructuring: const Home = ({ tasksData }: Home) => { }
I also have a React Context provider in _app.tsx called BoardProvider. This stores state for my task board, and employs useReducer() from the React Context API to update this state in context consumers, such as pages/index.tsx.
The challenge I am facing is how to make my UI's "source of truth" the state stored in my context provider (eg. const { { tasks }, dispatch } = useBoard();, rather than the page page props returned from my API (eg. the tasksData prop).
One approach I considered was to simply load the data in getServerSideProps and then set the state via a dispatched action in a useEffect hook:
useEffect(() => {
// On first render only, set the Context Provider state with data from my page props.
dispatch({ type: TaskAction.SET_TASKS, payload: tasksData });
});
However, this doesn't seem to be working because sometimes tasksData is undefined, presumably because Next.js has not yet made it available on page mount.
Another suggestion I heard was to fetch the data and pass it as pageProps to my Context Provider in _app.tsx. I believe this means using getInitialProps() in _app.tsx so that my provider's initial state is populated by my API. However, this disabled static optimization and other useful features.
Can anyone help me out with some pseudocode, documentation, or examples of how to use getServerSideProps in combination with React Context API?
Couple of points:
getServerSideProps should be invoked before the page is even rendered. So theoretically your tasksData is undefined is a bug! You can't have a server data to be unavailable unless you really really intend to have that happen in the first place.
Assuming getServerSideProps is always returning the right data, but you want to use your own data to override it. In your context, you can have this logic.
const Home = ({ tasksData }) => {
const value = { tasksData: {
// let me override it
}}
return (
<Context.Provider value={value}>
...
<Context.Provider>
)
}
If you have the context provided under a page, the above code is all you need. But if your context is provided in a root (parent of a page), you can still add the above code to re-provide the same context again with overridden value. Because this is how a context is designed, read https://javascript.plainenglish.io/react-context-is-a-global-variable-b4b049812028 for more info.
I have React app in framework Next.js (but framework is not important) with authentification implemented.
Current app is getting user session data by API call after first load (I am using next-auth for this). This causes that there is interval between load and session fetch which results in small interval where user see loading spinners all over app.
I want to mitigate this with implementation of SSR (Server-side-rendering) where I can hydrate initial response with session data.
This can be easily done in Next.js by getServerProps method in Component, but the problem is that I need to implement this method on every single page I have.
Thats why I am searching for a way to make session state global across whole app so every page (component) can access this session data.
My main question is, if I should use HOC wrapper where I will implement getServerProps to receive session data, or I should use Redux store and hydrate this store with by same method.
I am not exactly sure if HOCs are still widely used, or they are just deprecated now.
I do not want to implement getInitialProps in Next.js custom _app.js file because this will disable ability to have statically genererated pages and session will be available in every page. I am planning that app needs this session data only in about half of pages.
So should I go with HOC or just inplement redux store for this?
You could do the following:
Create an authentication Context Provider that will pass auth data down to your components. As an example:
interface ContextProps {
authenticated: boolean;
handleLogin: () => void;
handleLogout: () => void;
}
const authContext = createContext<Partial<ContextProps>>(undefined);
Along with Context, create a hook that will make use of useEffect to determine authenticated state - could be checking cookie exists, or await call to API to check auth status. Set this as the relevant status and use this value in your provider.
interface AuthProviderProps {
children: any;
}
const AuthProvider = (({ children }: AuthProviderProps) => {
const auth = useProvideAuth();
return <authContext.Provider value={auth}>{children}</authContext.Provider>;
});
export const useAuth = () => useContext(authContext);
export default AuthProvider;
Wrap in your _app.js:
...
<AuthProvider>
<Component pageProps={pageProps} />
</AuthProvider>
...
Access in relevant pages:
const Page = () => {
const { authenticated } = useAuth();
if (!authenticated) {
return (
<>
Not authenticated
</>
);
}
...
Then you can consume in your relevant components
Summary:
1) Do you know how to keep the state of a Context Provider present when it is mounted/unmounted through routing?
2) Or do you know a well maintained Flux implementation that supports multiple separated stores?
In detail:
Besides React components own state I've been using mostly redux so far. Besides not loving the idea of having every state managed globally, even though it might only be relevant for a subtree, it also becomes an issue for my new project.
We want to dynamically load components and add them via routing to the app. To be able to have components ready for plug and play, we want them to take care of their own state (store it, request it from the server, provide a strategy to modify it).
I read about how to dynamically add reducers to the global store with redux, but I actually find the approach of Reacts Context API much nicer where I can encapsulate some state in a Provider and can consume it wherever I need it.
The only issue I have is, that a Provider and a Consumer are React components, so if they are part of a component, that is mounted and unmounted through routing, the state that might have been created or fetched once, is gone.
It seems to me that there is no way to solve that, besides temporarily storing the state in the localstorage or on the server. If there is, please let me know!!!
If there shouldn't be a better solution:
I also thought about a more original Flux implementation that would allow multiple stores, which could be encapsulated with the relavant component subtree. But I haven't really found any well maintained Flux implementation besides Redux. Mobx being the exception, but I really prefer the reducer solution of Redux over the observable solution of Mobx. So again, if you know a multi store Flux implementation that is well maintained, please let me know!!!
I would be really happy about some feedback and hope you can point me into a direction that is more satisfiying than dynamic reducer Redux or temporarily persisted Context state.
Thanks a lot in advance!
Sorry that it's quite a late answer
Are you using React Router?
The state should be persisted and it shouldn't clear if you are navigating correctly. There should be no page reload as this will cause the state to clear.
Here is an example:
import { Router as RootRouter } from 'react-router-dom';
import Router from './routes/Router';
const App = () => {
return (
<MyContext value={useReducer(myReducer, initialState)}>
<RootRouter history={browserHistory}>
<Router />
</RootRouter>
</AuthContext>
);
}
import About from '../components/About';
const Router = () => {
return (
<Switch>
<Route exact path='/about' component={About}></Route>
</Switch>
}
On your main home component, you have to use a Link or Navlink to "switch" between components. Therefore, you'll have something like...
import { Link } from 'react-router-dom';
<Link to="/about">About</Link>
This will navigate you to the about page in which you can still access the context stage where nothing is cleared.
So I figured out a way to work around the problem with Context (first question): I store the state of the Provider component in a variable. That way, when that component is mounted again, it uses the "persisted" state as the initial value for it's state.
let persistedState = {};
const Context = React.createContext();
export class Provider extends React.PureComponent {
state = { ...persistedState };
updateState = (nextState) => this.setState(nextState, () => {
persistedState = {...this.state};
});
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
I’m building this ReactJS application which where I want to upload an image using a click of a button and take it to the next view which is the cropping window and then after cropping it, then I can upload it to the server.
The issue which I’m having right now is how to take an image from 1 view to another view without uploading it to the server?
I tried taking the image path, but unfortunately when the UI refreshes, the path will get flushed.
So, what are the options which I can use in this situation.
Thank you!
Yesterday I worked on similar case. I'm using react-dropzone which returns uploaded files, one of the field in file object is 'preview', it contains uri of currently uploaded file (It's probably cached somehow, but I didn't investigate it yet).
In another view you just doing
So you want to keep your image infos between view. By "view", do you mean a different HTML page? I guess a more standard way of doing things would be to:
store the file content/path in a state (e.g. redux state)
use client-side router (e.g. react-router) to change the view while keeping the state
If you have never used client-side routing, it looks like that (with react-router):
import { Router, Route, browserHistory } from 'react-router'
// Iy you use redux to handle the state
import { createStore } from 'redux'
import myReducer from 'my-reducer'
const store = createStore(myReducer)
const history = syncHistoryWithStore(browserHistory, store)
// Your view components
function Top(props) { ... }
function UploadImage(props) { ... }
function EditImage(props) { ... }
// The Router itself
ReactDOM.render(
<Router history={history}>
<Route path="/" component={Top} >
<Route path="upload-image" component={UploadImage} />
<Route path="edit-image" component={EditImage} />
</Route>
</Router>)
If you have never used redux before, you can use it this way:
First, create the reducer
import { combineReducers } from 'redux'
const myReducer = combineReducers({ imagePath })
// This is called a reducer function
function imagePath(oldState = "", action) {
switch(action.type) {
case 'SET_PATH':
return action.payload
}
}
Next, connect your component to get the state value (e.g. UploadImage)
const ReduxUploadImage = connect(function(state) {
return {
imagePath: state.imagePath
}
})(UploadImage)
Now if you use ReduxUploadImage instead of UploadImage, you can access imagePath through props.imagePath. The connect function also add a dispatch function to your props.
Finally, you can set the path by calling within your component (but not the render function itself: this would be an anti-pattern)
props.dispatch( { type: 'SET_PATH', payload: "the_path" } )
Finally, it is easy to persist the redux state between pages or refresh using dedicated middleware.
The IndexedDB API is great for client side, large file storage.
Using IndexedDB: MDN
Checkout LocalForage. It makes using IndexedDB easy, similar to using localStorage.
This solution would allow you to store an image client side with all major browsers. You would be able to perform operations on it from a global store and when you wanted to, you would be able to send it to the server.
It also provides your application with an offline cache, where the image would not be lost between sessions or network connections.
You need to read the image file as a blob and keep it on a parent component which would be in charge of rendering the different components for image manipulation. In some file input element you need a function like this to handle the image selected by the user
handleImageChange = e => {
e.preventDefault();
const reader = new FileReader();
const file = e.target.files[0];
reader.onloadend = () => {
this.setState({
file,
imagePreview: reader.result
});
};
if (file) {
reader.readAsDataURL(file);
}
};
Then you can put that file on as many components as you like. If you want it to persist between refreshes you need to put the file on localStorage or indexedDB
can anyone tell me how to call an action when you change the URL of the Route in server side react redux component with react-router for routing.
I am using browserHistory of react-router to push my inputs to url.
import React from 'react';
import { connect } from 'react-redux';
import { browserHistory } from 'react-router';
class findBirthdays extends Component {
constructor(props){
super(props);
}
openCalender(){
// here I open the date picker and let user select the date
// then I add that date in the URL as "localhost:3000/find/?date=22-02-2017"
// here I get confused as I do not know now how to dispatch the action or
// how to access url params in a specific actions after dispatching that action
}
render(){
return (
// repeating all the birthday that are available on selected date
)
}
}
const mapStateToProps = (state) => {
return {
birthdayList: state.birthdays,
};
};
export default connect(mapStateToProps)(findBirthdays);
I have many inputs and on every input change event, will be appending the URL and that should lead to dispatching an action and getting the proper results.
I am new to react and redux, so if anyone can suggest if this can be done or how to approach this problem that would be helpful.
Thanks guys.
when URL changes componentWillRecevieProps will call automatically there you can access nextProps then you can dispatch any action you want
in nextProps you can access location that has everything u need about url
Sorry guys I missed something in my code, I solved this by following ways,
Whenever I will change input I will dispatch an action and update the URL.
so in above example, I will dispatch an action in
openCalender() function after selecting the date.
When a user wants to change the URL manually then I handle that on the server side.
When user will change the URL and press enter that means server renders the whole new page.
I am dispatching a default action on page load with,
this.props.location.query
params, which provided by react-redux-router.