Image upload with ReactJS - reactjs

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

Related

Initialize Redux Toolkit store via url params on Next 12+ SSR

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 :)

React router provides javascript object instead of original type on url address bar reload

I am explining my problem with just the relevant code, as the full example is in this codesandbox link.
I am passing some props through a link to a component.
These props, have a firebase timestamp.
The props are passed correctly when the component is called through the link.
Link:
<Link to={{
pathname:path,
state: {
project
},
}} key={project.id}>
<ProjectSummary project={project} deleteCallback={projectDelete}/>
</Link>
Route:
<Route
path='/project/:id'
render={({ location }: {location: Location<{project: IFirebaseProject}>}) => {
const { state } = location;
const returnedComponent = state ? <ProjectDetails project={state.project} /> :
<ProjectDetails project={undefined}/>;
return returnedComponent;
}}
/>
and received by the ProjectList component, like this:
<div>{moment(stateProject.createdAt.toDate()).calendar()}</div>
My problem is that when the component is called through the link, props are passed and everything works fine, but, when I re-enter in the url adress bar, as the access to the component is not through the link, I would expect that the Route's render returned an undefined project (check route:
const returnedComponent = state ? <ProjectDetails project={state.project} /> : <ProjectDetails project={undefined}/>;) but, it returns the last passed project, with the timestamp as a plain Javascript object instead of a Timestamp type. So I get the error:
TypeError: stateProject.createdAt.toDate is not a function
Because the toDate() function is not available in the plain Javascript object returned, it is the Timestamp firebase type. Seems that for this specific case, the router is keeping it as a plain js object, instead of the original Timestamp instance. I would expect the route to return always the proyect undefined if not called from the link, as the props are not passed in (supposedly), but its not the case on the reload from the url address bar.
Curiously, in the codesandbox project, it does not reproduce, it fetches the data (you will be able to see the console.log('project fetched!!') when the project received is undefined).
However thrown from the dev server it happens. Might have something to do.
Find the git url if you wish to clone and check: https://github.com/LuisMerinoP/my-app.git
Remember that to reproduce you just need to enter to the link, and then put the focus in the explorer url address bar en press enter.
I case this might be the expected behaviour, maybe there is a more elegant way to way to deal with this specific case instead of checking the type returned on the reload. I wonder if it can be known if it is being called from the address bar instead of the link.
I know I can check the type in my component and fix this, creating a new timeStamp in the component from the js object returned, but I do not expect this behaviour from the router and would like to understand what is happenning.
Problem: Non-Serializable State
It returns the last passed project, with the timestamp as a plain Javascript object instead of a Timestamp type
I do not expect this behaviour from the router and would like to understand what is happening.
What's going on is that the state is being serialized and then deserialized, which means it's being converted to a JSON string representation and back. You will preserve any properties but the your methods.
The docs should probably be more explicit about this but you should not store anything that is not serializable. Under the hood React Router DOM uses the browser's History API and those docs make it more clear.
Suggestions
as in typescript is an assertion. It how you tell the compiler "use this type even though it's not really this type". When you have something that really is the type then do not use as. Instead apply a type to the variable: const project: IFirebaseProject = {
Your getProjectId function to get an id from a URL is not necessary because React Router can do this already! Use the useParams hook.
Don't duplicate props in state. You always want a "single source of truth".
Fetching Data
I played with your code a lot because at first I thought that you weren't loading the project at all when the page was accessed directly. I later realized that you were but by then I'd already rewritten everything!
Every URL on your site needs to be able to load on its own regardless of how it was accessed so you need some mechanism to load the appropriate project data from just an id. In order to minimize fetching you can store the projects in the state of the shared parent App, in a React context, or through a global state like Redux. Firestore has some built-in caching mechanisms that I am not too familiar with.
Since right now you are using dummy placeholder data, you want to build a way to access the data that you can later replace your real way. I am creating a hook useProject that takes the id and returns the project. Later on just replace that hook with a better one!
import { IFirebaseProject } from "../types";
import { projects } from "./sample-data";
/**
* hook to fetch a project by id
* might initially return undefined and then resolve to a project
* right now uses dummy data but can modify later
*/
const useProject_dummy = (id: string): IFirebaseProject | undefined => {
return projects.find((project) => project.id === id);
};
import { IFirebaseProject } from "../types";
import { useState, useEffect } from "react";
import db from "./db";
/**
* has the same signature so can be used interchangeably
*/
const useProject_firebase = (id: string): IFirebaseProject | undefined => {
const [project, setProject] = useState<IFirebaseProject | undefined>();
useEffect(() => {
// TODO: needs a cleanup function
const get = async () => {
try {
const doc = await db.collection("projects").doc(id).get();
const data = doc.data();
//is this this right type? Might need to manipulate the object
setProject(data as IFirebaseProject);
} catch (error) {
console.error(error);
}
};
get();
}, [id]);
return project;
};
You can separate the rendering of a single project page from the logic associated with getting a project from the URL.
const RenderProjectDetails = ({ project }: { project: IFirebaseProject }) => {
return (
<div className="container section project-details">
...
const ProjectDetailsScreen = () => {
// get the id from the URL
const { id } = useParams<{ id: string }>();
// get the project from the hook
const project = useProject(id ?? "");
if (project) {
return <RenderProjectDetails project={project} />;
} else {
return (
<div>
<p> Loading project... </p>
</div>
);
}
};
Code Sandbox Link

Read the current full URL with React?

How do I get the full URL from within a ReactJS component?
I'm thinking it should be something like this.props.location but it is undefined
window.location.href is what you're looking for.
If you need the full path of your URL, you can use vanilla Javascript:
window.location.href
To get just the path (minus domain name), you can use:
window.location.pathname
console.log(window.location.pathname); //yields: "/js" (where snippets run)
console.log(window.location.href); //yields: "https://stacksnippets.net/js"
Source: Location pathname Property - W3Schools
If you are not already using "react-router" you can install it using:
yarn add react-router
then in a React.Component within a "Route", you can call:
this.props.location.pathname
This returns the path, not including the domain name.
Thanks #abdulla-zulqarnain!
window.location.href is what you need. But also if you are using react router you might find useful checking out useLocation and useHistory hooks.
Both create an object with a pathname attribute you can read and are useful for a bunch of other stuff. Here's a youtube video explaining react router hooks
Both will give you what you need (without the domain name):
import { useHistory ,useLocation } from 'react-router-dom';
const location = useLocation()
location.pathname
const history = useHistory()
history.location.pathname
this.props.location is a react-router feature, you'll have to install if you want to use it.
Note: doesn't return the full url.
Plain JS :
window.location.href // Returns full path, with domain name
window.location.origin // returns window domain url Ex : "https://stackoverflow.com"
window.location.pathname // returns relative path, without domain name
Using react-router
this.props.location.pathname // returns relative path, without domain name
Using react Hook
const location = useLocation(); // React Hook
console.log(location.pathname); // returns relative path, without domain name
You are getting undefined because you probably have the components outside React Router.
Remember that you need to make sure that the component from which you are calling this.props.location is inside a <Route /> component such as this:
<Route path="/dashboard" component={Dashboard} />
Then inside the Dashboard component, you have access to this.props.location...
Just to add a little further documentation to this page - I have been struggling with this problem for a while.
As said above, the easiest way to get the URL is via window.location.href.
we can then extract parts of the URL through vanilla Javascript by using let urlElements = window.location.href.split('/')
We would then console.log(urlElements) to see the Array of elements produced by calling .split() on the URL.
Once you have found which index in the array you want to access, you can then assigned this to a variable
let urlElelement = (urlElements[0])
And now you can use the value of urlElement, which will be the specific part of your URL, wherever you want.
To get the current router instance or current location you have to create a Higher order component with withRouter from react-router-dom. otherwise, when you are trying to access this.props.location it will return undefined
Example
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
class className extends Component {
render(){
return(
....
)
}
}
export default withRouter(className)
Read this I found the solution of React / NextJs. Because if we use directly used the window.location.href in react or nextjs it throw error like
Server Error
ReferenceError: window is not defined
import React, { useState, useEffect } from "react";
const Product = ({ product }) => {
const [pageURL, setPageURL] = useState(0);
useEffect(() => {
setPageURL(window.location.href);
})
return (
<div>
<h3>{pageURL}</h3>
</div>
);
};
Note:
https://medium.com/frontend-digest/why-is-window-not-defined-in-nextjs-44daf7b4604e#:~:text=NextJS%20is%20a%20framework%20that,is%20not%20run%20in%20NodeJS.
As somebody else mentioned, first you need react-router package. But location object that it provides you with contains parsed url.
But if you want full url badly without accessing global variables, I believe the fastest way to do that would be
...
const getA = memoize(() => document.createElement('a'));
const getCleanA = () => Object.assign(getA(), { href: '' });
const MyComponent = ({ location }) => {
const { href } = Object.assign(getCleanA(), location);
...
href is the one containing a full url.
For memoize I usually use lodash, it's implemented that way mostly to avoid creating new element without necessity.
P.S.: Of course is you're not restricted by ancient browsers you might want to try new URL() thing, but basically entire situation is more or less pointless, because you access global variable in one or another way. So why not to use window.location.href instead?

How to hydrate server-side parameters with React + Redux

I have a universal React app that is using Redux and React Router. Some of my routes include parameters that, on the client, will trigger an AJAX request to hydrate the data for display. On the server, these requests could be fulfilled synchronously, and rendered on the first request.
The problem I'm running into is this: By the time any lifecycle method (e.g. componentWillMount) is called on a routed component, it's too late to dispatch a Redux action that will be reflected in the first render.
Here is a simplified view of my server-side rendering code:
routes.js
export default getRoutes (store) {
return (
<Route path='/' component={App}>
<Route path='foo' component={FooLayout}>
<Route path='view/:id' component={FooViewContainer} />
</Route>
</Route>
)
}
server.js
let store = configureStore()
let routes = getRoutes()
let history = createMemoryHistory(req.path)
let location = req.originalUrl
match({ history, routes, location }, (err, redirectLocation, renderProps) => {
if (redirectLocation) {
// redirect
} else if (err) {
// 500
} else if (!renderProps) {
// 404
} else {
let bodyMarkup = ReactDOMServer.renderToString(
<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>)
res.status(200).send('<!DOCTYPE html>' +
ReactDOMServer.renderToStaticMarkup(<Html body={bodyMarkup} />))
}
})
When the FooViewContainer component is constructed on the server, its props for the first render will already be fixed. Any action I dispatch to the store will not be reflected in the first call to render(), which means that they won't be reflected in what's delivered on the page request.
The id parameter that React Router passes along isn't, by itself, useful for that first render. I need to synchronously hydrate that value into a proper object. Where should I put this hydration?
One solution would be to put it, inline, inside the render() method, for instances where it's invoked on the server. This seems obviously incorrect to me because 1) it semantically makes no sense, and 2) whatever data it collects wouldn't be properly dispatched to the store.
Another solution which I have seen is to add a static fetchData method to each of the container components in the Router chain. e.g. something like this:
FooViewContainer.js
class FooViewContainer extends React.Component {
static fetchData (query, params, store, history) {
store.dispatch(hydrateFoo(loadFooByIdSync(params.id)))
}
...
}
server.js
let { query, params } = renderProps
renderProps.components.forEach(comp =>
if (comp.WrappedComponent && comp.WrappedComponent.fetchData) {
comp.WrappedComponent.fetchData(query, params, store, history)
}
})
I feel there must be better approach than this. Not only does it seem to be fairly inelegant (is .WrappedComponent a dependable interface?), but it also doesn't work with higher-order components. If any of the routed component classes is wrapped by anything other than connect() this will stop working.
What am I missing here?
I recently wrote an article around this requirement, but it does require the use of redux-sagas. It does pickup from the point of view of redux-thunks and using this static fetchData/need pattern.
https://medium.com/#navgarcha7891/react-server-side-rendering-with-simple-redux-store-hydration-9f77ab66900a
I think this saga approach is far more cleaner and simpler to reason about but that might just be my opinion :)
There doesn't appear to be a more idiomatic way to do this than the fetchData approach I included in my original question. Although it still seems inelegant to me, it has fewer problems than I initially realized:
.WrappedComponent is a stable interface, but the reference isn't needed anyway. The Redux connect function automatically hoists any static methods from the original class into its wrapper.
Any other higher-order component that wraps a Redux-bound container also needs to hoist (or pass through) any static methods.
There may be other considerations I am not seeing, but I've settled on a helper method like this in my server.js file:
function prefetchComponentData (renderProps, store) {
let { params, components, location } = renderProps
components.forEach(componentClass => {
if (componentClass && typeof componentClass.prefetchData === 'function') {
componentClass.prefetchData({ store, params, location })
}
})
}

Redux router - how to replay state after refresh?

I have a multi-step form application, and I'm struggling with getting my head around how I can save my redux state and replay it after a refresh for example? Going back/forward in the app works as expected, but after a browser refresh, my previous state is empty. Ideally I'd like to be able to save prior state in session storage relating to a path, so that I can replay later, but I'm not seeing how I can do that easily. Has anyone done anything like this and can provide some pointers? Thanks.
It looks like you're trying to use a single-page app framework within a multiple-page context. To make the two play nicer together, you could look into making your own middleware that synchronizes state to and from localStorage to create an app that appears to not have lost any state after a refresh/page navigation.
Similar to the way that redux-logger logs both the previous and next states, I'd probably start by plugging in a middleware at the beginning (localStorageLoad) and end (localStorageDump) of the createStoreWithMiddleware function creation (right before redux-logger):
// store/configureStore.js
const createStoreWithMiddleware = applyMiddleware(
localStorageLoad, thunk, promise, localStorageDump, logger
)(createStore);
Then fire an initial action right when you initialize your app to load stored state before your app renders:
// index.js
const store = configureStore();
store.dispatch({ type: 'INIT' });
ReactDOM.render(<App />, document.getElementById('root'));
The localStorageLoad would handle the INIT action and dispatch some sort of SET_STATE action, which would contain a payload with the state that was previously saved in localStorage.
// middleware/localStorageLoad.js
export default store => next => action => {
const { type } = action;
if (type === 'INIT') {
try {
const storedState = JSON.parse(
localStorage.getItem('YOUR_APP_NAME')
);
if (storedState) {
store.dispatch({
type: 'RESET_STATE',
payload: storedState
});
}
return;
} catch (e) {
// Unable to load or parse stored state, proceed as usual
}
}
next(action);
}
Then, add a reducer which replaces the state with that payload, effectively rebooting the app as it was previously.
To complete the loop, you'd need a localStorageDump middleware that comes at the end of the chain that saves each reduced state object into localStorage. Something like this:
// middleware/localStorageDump.js
export default store => next => action => {
const state = store.getState();
localStorage.setItem('YOUR_APP_NAME', JSON.stringify(state));
next(action);
}
Just an idea, haven't actually tried it. Hope that helps get you started towards a solution.
You can't store state on refresh this is normal. Typically you store these inside cookies or web storage.
Cookies are synced between browser, server and are set on every request you perform.
Localstorage gives you up to 5MB to store data in and never gets send with the request.
Redux:
Set localstorage properties when filling in form. (be sure to clean it when complete)
Refresh the page with an incomplete form.
When react mounts your Form component, use componentWillMount method to read the localstorage and dispatch the data to a reducer
The reducer updates the redux-state which in turn updates the component with the properties you got from localstorage.
If you use redux to store form data dispatch the properties of localstorage.
If not read the localstorage inside the Form component and set the initial values.
Hope this helps !

Resources