How do I render a user's previous session with React? - javascript

I am building a webapp using React js that allows users to add text, alter it, add images, alter them, and upload their own images.
I want to be able to reload the page as they left it if they close the tab or go to a different page. I know that I can store with localStorage, however I am having trouble rendering that when they return to that page. How do I inject that localStorage into the body so that it renders that session?
I'm using the MERN stack to handle user data. But in this case I don't necessarily want to store the JSON string representation of the page. I just want to keep it in the browser's local storage, and render the elements as they were before the user left (position, dimensions, etc).

Maybe something like this?
localState.js
export const clearState = () => {
localStorage.removeItem('state');
}
export const loadState = () => {
try {
const state = localStorage.getItem('state');
if (state === null) {
return {};
}
return JSON.parse(state)
} catch (err) {
console.warn('%c load state error', 'color: #b00', err);
}
};
export const saveState = (state) => {
try {
localStorage.setItem('state', JSON.stringify(state));
} catch (err) {
console.warn('%c save state error', 'color: #b00', err);
}
};
index.js
import { createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import reducers from './reducers.js';
import { clearState, loadState, saveState} from './localState.js';
const initialState = loadState();
const store = createStore(
reducers,
initialState,
composeWithDevTools(applyMiddleware(...middlewares)), // not necessary, but nice for debugging
);
// save the state whenever the Redux store changes
store.subscribe(() => {
const state = store.getState();
if (state.user.signOut) {
clearState();
} else {
saveState(store.getState());
}
});
ReactDOM.render(
<AppRoot store={store} />,
document.getElementById('root')
);
One caveat is that if you ever change the schema, you'll want to blow away the saved state to avoid weird bugs. Or you could use schema versioning.

If you would like the web application to show user data you'll need to use some sort of database. MySQL and PostgreSQL are great SQL databases. If you've never worked with a SQL database before MySQL is good to start off with. If you don't want to use a SQL database you can use a NoSQL database which uses .json files to represent your data. There are tools to help you manage .json data files like mongoDB. Note if you plan on scaling this application really large SQL databases are better where as a NoSQL database are more suited for prototyping.
Also you will need a hosting server to download these databases and serve your content to the public web. The are many cloud hosting providers out there like Amazon web services, Microsoft Azure, Google Cloud, Digital Ocean, and Linode to name a few of the more popular ones.

Related

redux toolkit using older data in slice to minimize calls to server

I'm fairly new to developing web apps, I started learning react + redux toolkit while using Django as a backend framework
to my point,
I was trying to minimize calls to the server by using a useEffect to check if the value of a specified selector is filled with data, so then I can use that data instead of calling the server again
now when I make the check
useEffect(() => {
flights.value.length <= 0 && dispatch(fetchFlights())
// eslint-disable-next-line
}, [])
it works when you first call the component
but after that, every time I open that component (whether I click on its link, or using a navigate(-1) to go back to that component) it won't display anything. I'll need to manually refresh the page for it to work correctly
this is for the component to render the data via a map function (works as it displays it when first calling it)
{!logged ? <Login /> : flights.loading ? <div>loading..</div> : flights.value.length > 0 && flights.value.map(...)}
now if i change the useEffect to this:
useEffect(() => {
dispatch(fetchFlights())
// eslint-disable-next-line
}, [])
basically without the data check, it works just fine
I was wondering if there is a way to check for the data and have it displayed without a call to the server again
or hear your thoughts about calling the server again and again and maybe its just better that way?
If you are using redux-toolkit, createApi feature is the best option. You can use the fetched data across your app without retrieving it multiple times or refresh the obtained data based on your needs (polling, caching, manual refetching, invalidating it after a certain time... )
// Need to use the React-specific entry point to allow generating React hooks
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react'
// Define a service using a base URL and expected endpoints
export const fligthsApi = createApi({
reducerPath: 'flights',
baseQuery: fetchBaseQuery({ baseUrl: 'https://yourapi.com' }),
endpoints: (builder) => ({
getFlights: builder.query({
query: () => `/yourFlightsPath`,
}),
}),
})
// Export hooks for usage in function components, which are
// auto-generated based on the defined endpoints
export const { useGetFligthsQuery } = fligthsApi
The you can use it in your app like:
export default function App() {
// Even if this component is unmount, flights data will be cached
const { data, error, isLoading } = useGetFligthsQuery()
// render UI based on data and loading state
}
(This is a minimal example, complete working code needs importing the api in your store)

How redux works with next.js?

I need to integrate redux with an existing next project. but currently, I don´t understand how the store works server-side.
I´m following this example:
https://github.com/vercel/next.js/blob/canary/examples/with-redux/pages/ssg.js
export const initializeStore = (preloadedState) => {
let _store = store ?? initStore(preloadedState)
// After navigating to a page with an initial Redux state, merge that state
// with the current state in the store, and create a new store
if (preloadedState && store) {
_store = initStore({
...store.getState(),
...preloadedState,
})
// Reset the current store
store = undefined
}
// For SSG and SSR always create a new store
if (typeof window === 'undefined') return _store
// Create the store once in the client
if (!store) store = _store
return _store
}
Why a store is created in the server, I´m asking is because in the client also is created so, what store is finally used.
Why do I need to create an store in the server if in the client another totally different is created?
import { Provider } from 'react-redux'
import { useStore } from '../store'
export default function App({ Component, pageProps }) {
const store = useStore(pageProps.initialReduxState)
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
)
}
The component definition below is also rendered in the client?
export function useStore(initialState) {
const store = useMemo(() => initializeStore(initialState), [initialState])
return store
}
It is for SEO purpose. When client makes a request to the server, if you fetch the data on the server, populate the store and send this data to the client, search engine crawlers will see what is your website about, they can read what you have sent.
But you do not need to fully populate the store on the server. For example, let's say you have an admin page, and admin can access to users of your app or website. So when yo fetch data on the server, your server will ship a bunch of names to the client and this will not effect on your SEO results. In this case, in admin page component, inside "useEffect" you can dispatch action to get the list of users from the server.
Let's say you have an ecommerce website and in index page you are showing your products. In this case, it will be better to populate the store on the server so search engine crawlers can read your products and it helps your SEO results. To sync the store with the client you also have to dispatch "hydrate" action from the client to pass the data to the client side store. I think in your code "initializeStore()" is handling that.

check if user first visit on app with react-native

I'm new to react and I want to show new users illustration of how my app work.
how can I achieve that?
Am I need to download something from npm?
You may use AsyncStorage for that. AsyncStorage is an unencrypted, asynchronous, persistent, key-value storage system that is global to the app. It should be used instead of LocalStorage.
import {AsyncStorage} from 'react-native';
const tutorialShown = async () => {
return await AsyncStorage.getItem('#myApp:TUTORIAL_SHOWN');
}
// somewhere in your render method
if (!tutorialShown()) {
return <Tutorial />
}
//somewhere in your Tutorial's componentDidMount
AsyncStorage.setItem('#myApp:TUTORIAL_SHOWN')
Details: https://facebook.github.io/react-native/docs/asyncstorage

React Native: HeadslessJS and Redux - How to access store from task

We have a ReactNative app that uses redux, redux-persist and a HeadlessJS task. This task needs to have access to the store. As the task fires without booting the entire app (and so has no access by default), we thought we could simply create the store inside the task as well so that it would be rehydrated by redux-persist. It turns out, however, that the store created in this way is different from the one in the app: after running, they contain different values. We tested this in several ways and it seems indeed a problem with the stores (and not with the actions for instance)
How should we access a Redux store from an HeadlessJS task?
Relevant code:
store/configure.js:
configureStore = (client) => {
const middleware = createMiddleware(client);
const finalCreateStore = applyMiddleware(thunk, middleware, logger)(createStore);
const store = finalCreateStore(rootReducer, undefined, autoRehydrate());
return store;
};
In use (both in the app and in the service):
const client = new ApiClient();
const store = configureStore(client);
client.setStore(store);
persistStore(store, {
storage: AsyncStorage,
}
In the app we simply use the Provider from react-redux to use the store, in the service we use store.dispatch.
For people looking for solution. I have found the solution in here.
The idea is to bind the store to async method.
https://github.com/react-native-kit/react-native-track-player/issues/63
Copy pasting the solution here.
// index
const store = ...
....registerHeadlessTask('TrackPlayer', () => require('event-handler.js').bind(null, store));
// event-handler.js
module.exports = async (store, data) {
if(data.type == '...') {
store.dispatch(...);
}
};
simply create the store inside the task as well so that it would be rehydrated by redux-persist.
This did indeed happen.
You created two stores (not advisable with redux) which were both hydrate, but not linked, as there is no such thing as linked redux stores.
Every time you run createStore, it's a new store. And every time you dispatch, you do that on a specific store.
Unfortunately async or multithreaded issues are not directly addressed by redux.
It would be possible though with middleware and / or store listeners to keep the two stores in sync.
But redux is also just not a mean for communication between threads (which I assume these tasks are, or you could just give the task a reference to the store once it was created or give the main app the store reference from the task).
It's more a form of Command-Query-Separation and centralized state.
You can access your store directly as reference.
Let's say you have your headless set in index.js, then you can just simply use store there like this:
import { AppRegistry } from 'react-native';
import Store from './src/Redux/Store';
import { someAction } from './src/Redux/Actions/someActions';
import App from './App';
import { name as appName } from './app.json';
const HeadlessTask = async () => {
console.log('Receiving HeadlessTask');
const someParam = await Store.getState().Something.someParam;
if (someParam) {
Store.dispatch(someAction(someParam));
} else {
Store.dispatch(someAction());
}
};
AppRegistry.registerHeadlessTask('HeadlessTask', () => HeadlessTask);
AppRegistry.registerComponent(appName, () => App);

Redux-thunk with Websockets

I want to create a websocket on demand when certain components want to subscribe to data. How can I share the websocket instance in a redux fashion?
action.js
export function subscribeToWS(url) {
return dispatch => {
let websocket = new WebSocket(url)
websocket.on('connect', () => {
websocket.send("subscribe")
}
websocket.on('message', (message) => {
dispatch(storeNewData(message))
}
}
}
I could do something like this, but this would require a new instance for every new subscription.
The standard place to put things like persistent connection objects is inside middleware. And, in fact, there's literally dozens of existing middlewares that demonstrate that approach, with most of them listed over at https://github.com/markerikson/redux-ecosystem-links/blob/master/middleware.md#sockets-and-adapters . You should be able to use some of those as-is, or at least as examples.
You can look at redux-websocket-bridge. It unfold Web Socket messages into Redux action, and relay Redux action to Web Socket.
Upside of this approach: you can use Redux on your server as API endpoint, replacing standard REST API with less code.
Also, if your server do not send Flux Standard Action, you can still use redux-websocket-bridge for raw messages. It works with string, ArrayBuffer, and Blob. Of course, you can always write a small middleware to translate them into Flux Standard Action, e.g. messages from Slack RTM API.
Although this is quite an old question by now, it popped up several times when looking for an example. As #matthewatabet and #abguy mention, https://github.com/luskhq/redux-ws just mentions it has been deprecated and you can use Redux Thunk, without an example specific for web sockets.
For future reference, I found this article that outlines an example, that is implemented in a Github repo, starting on this file. This is for socket.io, but using web sockets directly should be similar.
Summarizing, in the Component call dispatch with addNewItemSocket:
<RaisedButton
label="Click to add!" primary={true}
onTouchTap={ () => {
const newItem = ReactDOM.findDOMNode(this.refs.newTodo.input).value
newItem === "" ? alert("Item shouldn't be blank")
: dispatch(addNewItemSocket(socket,items.size,newItem))
{/*: dispatch(addNewItem(items.size,newItem))*/}
ReactDOM.findDOMNode(this.refs.newTodo.input).value = ""
}
}
/>
In the actions file, declare addNewItemSocket as:
export const addNewItemSocket = (socket,id,item) => {
return (dispatch) => {
let postData = {
id:id+1,
item:item,
completed:false
}
socket.emit('addItem',postData)
}
}
To handle incoming messages from the socket, in the constructor of the component:
socket.on('itemAdded',(res)=>{
console.dir(res)
dispatch(AddItem(res))
})
And in the actoins file, declare AddItem as:
export const AddItem = (data) => ({
type: "ADD_ITEM",
item: data.item,
itemId:data.id,
completed:data.completed
})
For me this is still new, so any feedback is appreciated. I will also file a PR with https://github.com/luskhq/redux-ws to have an example listed there.

Categories

Resources