How to handle multiple components in a loop with React-Redux - reactjs

I am working on a simple react-redux application. And I need my user to upload 2 or 5 files (depending on the user type). Let's say he can be either a simple user or an admin, the user has to upload 2 file and an admin 5. I use react-dropzone to upload my files
From my database I get an object with an array of object, it returns something like:
user: {
name: "John Doe",
docs: [
{
url: "http://path-to-image.png"
},
{
url: "http://path-to-image2.png"
}
]
}
To simplify, in my application I have a Container.js, a Component.js, and in the component i iterate in the docs array by doing a map, and then for each doc I render a new item, let's say FileItem.
The problem is more in the way to upload the files than how to handle the files from the database.
On each file I want to set some booleans in order to know if the user has chosen a picture and if the picture is submitting to the database.
So my actions look like this for now:
function changePictureA(picture){
return {
type: ADD_PICTURE_A,
picture: picture,
isPictureChanged: true,
isPictureSubmitting: false
}
}
export function changeFirstPicture(file){
return function(dispatch){
dispatch(changePictureA(file[0]));
}
}
This works well and I can see my action being dispatched but for now I have set this logic in the FileItem component. But like this file is going to be called several times (2 or 5 according to user's type), this logic can't be in this file right ?
What I really want to know is if I have to set all my values in my container, that mean to write something like this :
function mapStateToProps(state){
const { picture,isPictureChanged, isPictureSubmitting} = state.pictureAReducer;
return {
picture,
isPictureChanged,
isPictureSubmitting
}
}
export default connect(mapStateToProps)(KycItem);
So this is for one Item, if I have 5, I will have to do it 5 times and to have 5 reducers just for this ? Is that the best solution ? I mean, I will have to set long name variables like you can see whereas I would have prefer to just have isSubmitting, isChanged.
I hope your answers will help me !

This perfectly fits into redux todo list sample. Instead of creating reducers and controls for every separate picture you can just add id to the picture and use general actions:
function changePictureAction(id, picture){
return {
type: ADD_PICTURE,
id: id,
picture: picture,
isPictureChanged: true,
isPictureSubmitting: false
}
}
export function changePicture(id, file){
return function(dispatch){
dispatch(changePictureAction(id, file[id]));
}
}
Then you need to split Presentation and Container, on the presentation you need to have FileItem and FileItemsList, while Container handles all data binding logic:
// Container
import changePicture from './PictureActions'
import FileItemsList from './FileItemsList'
function mapStateToProps(state) {
return {
pictures: state.pictures
}
}
function mapDispatchToProps(dispatch) {
return { changePicture }
}
export default connect(mapStateToProps, mapDispatchToProps)(FileItemsList);
// FileItemsList.js
const FileItemsList = ({pictures, changePicture}) => {
const fileitems = pictures.map(pic =>
<FileItem key={pic.id} picture={pic.picture}
onChange={(picture)=>changePicture(pic.id, picture)}/>)
return <div>{fileitems}</div>
}
export default FileItemsList
Also you will need to add upload handlers and dispatchers

Related

How to access current value from writable?

I want to create a custom wrapper for i18n to translate content of the site by clicking the lang button.
Currently, I have something like this.
<script>
import { localization } from './localiztion.ts';
</script>
<p>{localization.t("hello")}</p>
<button on:click={localization.toggleLocale}></button>
p which holds a text (which should be translated) and button which triggers translation.
To split logic from UI I moved localization logic into a different file. It looks like this
const resources = {
"en": {
"hello": "Hello",
},
"uk": {
"hello": "Привіт"
}
}
export function createLocalization() {
let store = writable("en");
return {
unsubscribe: store.unsubscribe,
toggleLocale: () => {
store.update((previousLocale) => {
let nextLocale = previousLocale === "en" ? "uk" : "en";
return nextLocale;
});
},
t: (key: string): string => {
// How to get access to the current store value and return it back to UI?
// I need to do something like this
return resources[store][key]
}
}
}
export const localization = createLocalization();
The problem I have I need to access the current local from within a t function. How can I do this?
I could pass it from UI like
// cut
<p>{localization.t("hello", $localization)}</p>
// cut
by doing this I achieve what I want, but the solution is too cumbersome.
Any advice on how I can do this?
You could get the store value via get, but this is be a bad idea, as it would lose reactivity. I.e. a language change would not update your text on the page.
A better approach is defining it as a store. Since stores currently have to be at the top level to be used with $ syntax, it is more ergonomic to split it into a separate derived store:
export let locale = writable("en"); // Wrap it to restrict it more
export let translate = derived(
locale,
$locale => key => resources[$locale][key],
);
This way you can import this store, which contains a function for translating keys:
import { translate } from '...';
// ...
$translate('hello')
REPL
(The stores can of course also be created differently and e.g. injected via a context instead of importing them.)

What's the proper way to grab an object from my Entities dictionary in a Normalized state and pass it to a component?

I have a state that looks like:
entities: {
pageDict: {
['someId1']: { name: 'page1', id: 'someId1' },
['someId2']: { name: 'page2', id: 'someId2' },
['someId3']: { name: 'page3', id: 'someId3' }
}
}
lists: {
pageIdList: ['someId1', 'someId2', 'someId3']
}
And a Component that looks like:
const Pages = ( props ) => {
return (
<div>
props.pageIdList.map(( pageId, key ) => {
return (
<Page
key={ key }
pageObj={ somethingHere } // What to do here?
/>
);
})
</div>
);
}
To grab the object, I would need to do:
let pageObj = state.entities.pageDict[ pageId ];
I guess I can pass the state.entities.pageDict as props from the Containers.. but I'm trying to look at the selectors pattern and looking at this:
https://redux.js.org/docs/recipes/ComputingDerivedData.html
and I'm wondering if I'm doing this wrong, can someone give me some insight? I just want to make sure I'm doing this correctly.
EDIT: I got the idea of using Entities from the https://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html and the SoundCloud Redux project
EDIT2: I'm looking at things online like this https://github.com/reactjs/redux/issues/584 but I'm questioning if I'm using this incorrectly and I'm not sure how to apply this to my own project
EDIT3: I'm leaning on creating a selector that will get the pageIdList and return a list of the pageobjects from the pageDict.. that way I already have the object to pass into my Page component.
I think I follow what you're trying to do here. With Redux try thinking of your user interface as always displaying something immutable: rather than "passing something to it" it is "reading something from the state". That when when your state changes your user interface is updated (it isn't always this simple but it is a pretty good start).
If I read your answer correctly you have a Map of pages:
//data
{
id1: {...pageProperties} },
id2: {...pageProperties} },
id3: {...pageProperties} },
}
and your page list is the order these are displayed in:
ex:
[id2, id3, id1]
Your page object might look something like this:
//Page.js
class Page extends React.Component {
render() {
const { pageIdList, pageEntities } = this.props //note I'm using props because this is connected via redux
return (
<div>
{ pageIdList.map((pageId, index)=>{
return <Page key={index} pageEntity={pageEntities[pageId]} /> //I'm just pulling the object from the map here
}}
</div>
)
}
}
//connect is the Redux connect function
export default connect((store)=> { //most examples use "state" here, but it is the redux store
return {
pageEntities: store.pageEntities,
pageIdList: store.pageList
}
})(Page)
Now when we want to change something you update the state via a dispatch / action. That is reduced in the reducer to display the new state. There are a lot of example out there on how this works but the general idea is update the state and the components take care of displaying it.
The result of this code (and there might be some typeos) is that you should see:
Page id: 2, Page id: 3, Page id: 1 because the list of the pages in the state is 2, 3, 1 in the example I gave.
To answer your question specifically what the entity I'm pulling is the global Redux Store (not the internal component state). 'Map State to Props' is really 'Map Store to Props' as the 'state' is part of React and the 'store' is your Redux store.
Does that help? React+Redux is really nice once you figure it out, it took me a solid month to understand all the ins and outs but it really does simplify things in the long run.

How do I access Redux items using selectors after I've normalized my store?

I'm a bit confused on how I'm supposed to use selectors after I've normalized my Redux store.
I have the following setup for my store:
const DEFAULT_STATE = {
allId: [],
locations: {}
};
With the following for my reducer:
handleActions({
['UPDATE']: (state, action) => {
let newID = state.allId.length;
const allId = [...state.allId, newID];
const locations = {
...state.locations,
[newID]: action.payload
};
return {allId, locations};
}
}),
...
I figured I would want something like this for my component:
function mapStateToProps(state) {
return {
share: callMySelector(state)
};
}
But I don't see how my selector would do anything except return the location associated with the most recent ID. I'm thinking that normalizing is also not that great here - because I wouldn't end up searching by ID in a regular case.
The power of selectors is that it moves filtering logic away from the component consuming the data, and away from the actions/reducer into reusable functions. You mentioned getting the most recent location. From the update logic in the reducer, we'd just make a selector that grabs the last item.
function selectLatestLocation(state) {
const latestId = state.allIds[state.allIds.length - 1];
return state.locations[latestId];
}
This assumes the location data is structured with the location id as the key.
{
1: { id: 1, name: "USA" },
2: { id: 2, name: "Europe" }
}
In this case, normalizing the data isn't doing much. But let's say requirements change, and now we only want Europe locations. We could have another state property called europeIds that contains all Europe location ids.
function selectEuropeLocations(state) {
return state.europeIds.map(id => state.locations[id]);
}
Using selectors with normalized Redux state make it really easy to change how data is filtered. Now like you said, some cases might not need to be normalized. It is really up to the project, and what is being accomplished. But, it's definitely worth it for data that needs to be memoized, or filtered in different ways!

React InstantSearch : onSearchStateChange get the number of returned hits

I am using the react instant search by algolia and i have a requirement to show an overlay every time there is result.
so i want to handle it via onSearchStateChange function provided by algolia. but i am still puzzled where to get the total hits. i already have an idea which is very quick like using the results displayed in the by extracting the numbers via jquery. but i don't want to do it. is there other way you can suggest?
onSearchStateChange(nextState) {
//must get the number of total hits.
nextState = cleanDeep(nextState);
let filters = transformer(nextState);
this.setState({
searchState: nextState,
filters: filters,
searchChanged: true
})
this.sendToAti(filters);
this.addOverlay(); // <--- function that will show the overlay.
location.hash = qs.stringify(nextState);
}
The onSearchStateChange function doesn't contain the searchResults object where you can find the number of hits.
However, we provide a <Stats> widget and a connectStats connector that contain this information. Maybe you could use that?
Basically, as pointed by #Marie and documented in the links she pointed, you need to follow a 3 step process:
1.- Create a custom component:
export default function MyStatefullComponent({ searchResults }){
const hasResults = searchResults && searchResults.nbHits !== 0;
const nbHits = searchResults && searchResults.nbHits;
//Handle State Mutations Here
return return (hasResults) ? <div className="shadow-xl mb-4 ml-4 p-8 ">
Has Results
</div>
:
<div>Has No Results</div>
}
2.- connect to the component using the connector
Once you have your custom component, use the connectStateResults
import { connectStateResults } from 'react-instantsearch-dom';
export default function OtherComponent(){
const StatefullComponent = connectStateComponent(MyStatefullComponent);
return <StatefullComponent />
}
3.- import/add the component to your other component.

React JS: Loop through a multi-dimensional array and determine/load the correct component

Using React, I'm fetching data from an API, a sample of which can be seen here.
I need to loop through the body section (multi-dimensional array) and then determine what type of 'block' it is:
heading
text
quote
frameImgeBlock
quoteImageBlock
frameQuoteBlock
Depending which block type it is then I need to create/load the corresponding React component (as I'm imagining it's better to separate these into individual React components).
How is it best to approach this in React/JS?
I may be able to tweak the API output a little if someone can suggest an easier approach there.
You could use a "translation" function like so, which would call external components:
// Import external deps
import Heading from './src/heading';
// Later in your component code
function firstKey(obj) {
return Object.keys(obj)[0];
}
getDomItem(item) {
const key = firstKey(item);
const val = item[val];
switch (key) {
case "heading":
return <Heading heading={ val.heading } ...etc... />
case "other key..."
return <OtherElm ...props... />
}
}
render() {
// data is your object
return data.content.body.map(item =>
this.getDomItem(item[firstKey(item)])
);
}

Resources