redux usage at general - javascript

im sorry for not-specific question, but Im confused a little bit. Im started learning redux, and now i need to implement it in Completely working project: https://github.com/CodeNinja1395/Test-task-for-inCode and there is a branch for redux-version (which is obviously isn`t working).
The main logic of my app located in ContactsApp.js file:
class ContactsApp extends Component {
constructor(){
super();
this.state = {
searchResults: [],
displayedUsers: [],
displayedInfo: [],
selectedUser: null,
searchValue: ''
}
}
componentWillMount() {
this.props.fetchData();
}
handleSearch(event){
let CONTACTS = this.props.items;
let inputValue = event.target.value;
this.setState({searchValue: inputValue});
let iterate = function(obj, callback) {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
if (typeof obj[property] == "object") {
iterate(obj[property], callback);
} else {
callback(obj[property]);
}
}
}
}
let searchResults = [];
CONTACTS.forEach(el => { //this finds all matches (except avatar)
let arr = [];
iterate(el, function (e) {
if (e!=el.general.avatar)
arr.push(e);
});
for (var i = 0; i < arr.length; i++) {
if(arr[i].toLowerCase().indexOf(inputValue) !== -1){
searchResults.push(el.foundValue = arr[i]);
}
}
});
var displayedUsers = CONTACTS.filter(el => { //this finds element by first match (except avatar)
let arr = [];
iterate(el, function (e) {
if (e!=el.general.avatar)
arr.push(e);
});
for (var i = 0; i < arr.length; i++) {
if(arr[i].toLowerCase().indexOf(inputValue) !== -1){
el.foundValue = arr[i];
return arr[i];
}
}
});
this.setState({searchResults: searchResults});
this.setState({displayedUsers: displayedUsers});
}
handleSelectedUser(user, color){
this.setState({selectedUser: user}, function (){
});
}
render() {
let users;
let selectElem = this.selectElement;
if (this.state.displayedUsers) {
users = this.state.displayedUsers.map(user => {
return (
<User
key={user.contact.phone}
user={user}
color={(user==this.state.selectedUser) ? 'LightSkyBlue ' : 'white'}
selectUser={this.handleSelectedUser.bind(this)}
searchValue={this.state.searchValue}
searchResults={this.state.searchResults}
/>
);
});
}
return (
<div>
<div className="left-column">
<div className="users">
<SearchUser handleEvent= {this.handleSearch.bind(this)} />
<ul className="usersList"> {users} </ul>
</div>
</div>
<div className="right-column">
<ContactDetail selectedUser={this.state.selectedUser} />
</div>
</div>
);
}
}
const mapStateToProps = state => ({
data: state.data
});
export default connect(mapStateToProps, {fetchData})(ContactsApp);
I know that the question is too abstract and probably when i learn redux better it will be obvious, but for i get stucked with this and dont know where to start.

I don't exactly get what your app is doing so my answer will be also quite generic.
Start with setting up the project with redux. You need redux and react-redux packages to get it up. Use a tutorial from redux official page. Install chrome redux dev tools extensions to be able to debug and look-up redux state.
Then go on with refactoring...
Start with isolating your data models. Probably you'll have a user and some kind of displayFilter. Create reducers for both of them.
Keep all users in user reducer and current state of filter in displayFilter. Set some mocked data as default state.
Combine filtering settings from displayFilter and list of users from user on the fly in mapStateToProps. Do your filtering of currently visible users in mapStateToProps so your component will get only the users that are intended to be visible.
Then add some dynamic stuff
Every time a user changes some filtering settings, dispatch an action with new settings and handle it in displayFilter, apply changes to the store. React-redux will automatically call your mapStateToProps and apply new list of filtered users to the component and trigger render.
Further steps
This is just couple of hints how to start. If you want your app to be performant and stable, you should familiarize with memoization concepts, reselect and immutable.js libraries.

Related

Bug CheckBox and useState Reactjs with Material UI

I have a numeric select, when I select a number the component is rendered N times, the component has several checkboxes inside if I activate one the others are activated and that should not happen
Annex code in sandbox
https://codesandbox.io/s/polished-hill-li6529?file=/src/App.js
I believe that issue here is that when React tries to update the state, it can't be sure of which element in the array to update, so it updates all of them to be safe.
One possible solution, with minimum code changes is to transform the state from an array to an object (having the index as key), so that when you set the state you can specify which key should be updated.
The code changes that are needed here are the following:
On the state initialisation (App.js):
const [wizard, setWizard] = useState({});
On wizard initialisation (App.js):
for (let count = 0; count < sizeSelect; count++) {
list[count] = { ...obj, id: count + 1, nameTable: `Users ${count}` }
}
On rendering the wizard (App.js):
{Object.values(wizard).map((service, index) => (
<Wizard .... />
))}
On handleChange() function (Wizard.js):
const handleChange = (code, value) => {
const auxWizard = {
...wizard,
[index]: {
...wizard[index],
apiServices: {
...wizard[index].apiServices,
[code]: {
...wizard[index].apiServices[code],
active: !value
}
}
}
};
setChange(auxWizard);
};

Creating a var referencing a store from redux

I am currently working on creating a var that references a store from redux. I created one but within the render(). I want to avoid that and have it called outside of the render. Here is an example of it. I was recommended on using componentWillMount(), but I am not sure how to use it. Here is a snippet of the code I implemented. Note: It works, but only when I render the data. I am using double JSON.parse since they are strings with \
render() {
var busData= store.getState().bus.bus;
var driverData= store.getState().driver.gdriveras;
var dataReady = false;
if (busData&& driverData) {
dataReady = true;
console.log("========Parsing bus data waterout========");
var bus_data_json = JSON.parse(JSON.parse(busData));
console.log(bus_data_json);
console.log("========Parsing driver data waterout========");
var driver_data_json = JSON.parse(JSON.parse(driverData));
console.log(driver_datat_json);
busDatajson.forEach(elem => {
elem.time = getFormattedDate(elem.time)
});
driverDatajson.forEach(elem => {
elem.time = getFormattedDate(elem.time)
});
...
}
}
Here is an example of react-redux usage that will probably help you.
Don't forget to add StoreProvider to your top three component (often named App).
I warned you about the fact that React and Redux are not meant to be used by beginner javascript developer. You should consider learn about immutability and functional programming.
// ----
const driverReducer = (state, action) => {
switch (action.type) {
// ...
case 'SET_BUS': // I assume the action type
return {
...state,
gdriveras: JSON.parse(action.gdriveras) // parse your data here or even better: when you get the response
}
// ...
}
}
// same for busReducer (or where you get the bus HTTP response)
// you can also format your time properties when you get the HTTP response
// In some other file (YourComponent.js)
class YourComponent extends Component {
render() {
const {
bus,
drivers
} = this.props
if (!bus || !drivers) {
return 'loading...'
}
const formatedBus = bus.map(item => ({
...item,
time: getFormattedDate(item.time)
}))
const formatedDrivers = drivers.map(item => ({
...item,
time: getFormattedDate(item.time)
}))
// return children
}
}
// this add bus & drivers as props to your component
const mapStateToProps = state => ({
bus: state.bus.bus,
drivers: state.driver.gdriveras
})
export default connect(mapStateToProps)(YourComponent)
// you have to add StoreProvider from react-redux, otherwise connect will not be aware of your store

Is it possible to hold a list of react components in state and render them?

I've been attempting to learn some React programming recently, and I ran into some confusion when learning about rendering lists.
The React documentation describes this method for rendering lists:
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
Out of curiosity, I decided to try holding a list of React elements in the state of one of my components and render that list directly. I figured that it might be able to avoid the need of mapping the data to an element every time a render occurred.
I was able to make this work:
'use strict';
class List2 extends React.Component {
constructor(props) {
super(props);
let nums = [];
for(let i = 0; i < 5; i++) {
nums.push(React.createElement('div', null, i));
}
this.state = {
numbers : nums,
}
}
render() {
return (
React.createElement('div', null, this.state.numbers)
)
}
}
However, I tried to add a button to my window that added elements to the element list, the new elements added by the button's onCLick function don't render. This is the code that doesn't work:
'use strict';
class List3 extends React.Component {
constructor(props) {
super(props);
this.state = {
nextNum : 1,
numbers : [],
}
this.buttonAction = this.buttonAction.bind(this);
}
buttonAction() {
let numElement = React.createElement('h1', null, this.state.nextNum);
let newNumber = this.state.numbers;
newNumber.push(numElement);
this.setState(
{ nextNum : (state.nextNum + 1),
numbers : newNumbers,
}
);
}
render() {
return (
React.createElement('div', null,
this.state.numbers,
React.createElement('button', {onClick : this.buttonAction}, 'clicketh')
)
)
}
}
When I click the button, I don't see new numbers render on the screen.
Can someone help me explain what's going on here?
Is the mapping method from above the only reliable way to render lists with react?
React only renders those elements which got change from last render, If you want to force render then you have to force react to do it otherwise it will not render new elements.
Arrays are reference type so if will add or remove elements to it, it will not create a new copy and react will consider it as unchanged.
For your render issue you need to create a new copy of "numbers" each time you add element so react will consider it as changed state and render as new.
you can achieve this by using map function in your render method which will provide react a new copy of array or use "slice" in your button click event so while setting new numbers state it will create a new shallow copy of "numbers" each time.
below are snippets for doing it in both ways.
buttonAction() {
let numElement = React.createElement('h1', { key: this.state.nextNum },
this.state.nextNum);
let newNumbers = this.state.numbers;
newNumbers.push(numElement);
this.setState(
{
nextNum: (this.state.nextNum + 1),
numbers: newNumbers.slice(),
}
);
}
Or
render() {
return (
React.createElement('div', null,
this.state.numbers.map(item=>item),
React.createElement('button', { onClick: this.buttonAction }, 'clicketh')
)
)
}
for more info on array please follow below link.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
Really not sure why you are doing this.
But maybe you can try something like this ?
this.setState({
numbers :[...this.state.numbers, numElement],
});
Creating a copy of numbers instead of reusing the old reference.

Need Help Simplifying an Immutable.js Array of Objects update

I'm new to immutablejs and have managed to update a property of an object stored in an array of objects. My goal is to simplify my development but I feel like I'v made it more complicated. Clearly, I'm missing something to make this simpler.
I created to stackblitz projects, one with the code without immutablejs https://stackblitz.com/edit/react-before-immutable , and one with immutablejs https://stackblitz.com/edit/react-after-immutable
(code below also).
I've seen some examples here where people use the second parameter of findIndex, but that function never got called for me. It also is not in the docs so I'm guessing it's not supported any more.
With Immutable.js
import React, { useState } from 'react';
import { List } from 'immutable';
export default () => {
const init = [{
id: 101,
interestLevel: 1
},
{
id: 102,
interestLevel: 0
}];
const [myArray, setMyArray] = useState(init);
const updateRow = (e) => {
const id = parseInt(e.target.attributes['data-id'].value);
const immutableMyArray = List(myArray);
const index = List(myArray).findIndex((item) => {
return item.id === id;
});
const myRow = immutableMyArray.get(index);
myRow.interestLevel++;
const newArray = immutableMyArray.set(index, myRow);
setMyArray(newArray);
};
return (
<ul>
{
myArray.map(function (val) {
return (
<li key={val.id}>
<button onClick={updateRow} data-id={val.id} >Update Title</button>
{val.id} : {val.interestLevel}
</li>
)
})
}
</ul>
)
}
Without Immutable.js
import React, { useState } from 'react';
export default () => {
const init = [{
id: 101,
interestLevel: 1
},
{
id: 102,
interestLevel: 0
}];
const [myArray, setMyArray] = useState(init);
const updateRow = (e) => {
const id = e.target.attributes['data-id'].value;
const newMyArray = [...myArray];
var index = newMyArray.findIndex(a=>a.id==id);
newMyArray[index].interestLevel = myArray[index].interestLevel + 1;
setMyArray(newMyArray);
}
return (
<ul>
{
myArray.map(function (val) {
return (
<li key={val.id}>
<button onClick={updateRow} data-id={val.id} >Update Title</button>
{val.id} : {val.interestLevel}
</li>
)
})
}
</ul>
)
}
Have you considered the purpose of immutablejs?
In your example, you are only adding needless complexity to your code, without leveraging the gains provided by the library.
The purpose of immutable is to provide immutable collections, inspired from scala. In other words, you create your collection, then you pass it to another component, and you can be certain that no element was appended or removed. The individual elements, however, are not under such guarantee, partly due to the constraints (or lack thereof) brought by JS.
As it stands in your code, there are very few reasons to do something like this. I've taken the liberty of changing your quote quite a bit in order to showcase how to do so:
class Comp extends React.Component {
constructor(props) {
super(constructor);
if (props.interests) {
this.state = {
interests: props.interests
}
} else {
this.state = {
interests: Immutable.Set([])
}
}
}
updateRow(e) {
return function() {
this.setState({
interests: this.state.interests.update((elements) => {
for (var element of elements) {
if (element.id == e) {
element.interestLevel++;
}
}
return elements;
})
});
}
}
render() {
var interests = this.state.interests;
var updateRow = this.updateRow;
var list = this;
//return (<div>Test</div>);
return ( <
ul > {
interests.map(function(val) {
return ( <
li key = {
val.id
} >
<
button onClick = {
updateRow(val.id).bind(list)
}
data-id = {
val.id
} > Update Title < /button> {
val.id
}: {
val.interestLevel
} <
/li>
)
})
} <
/ul>
)
}
}
var interests = Immutable.Set([{
id: 1,
interestLevel: 0
},
{
id: 2,
interestLevel: 0
}
])
ReactDOM.render( < Comp interests = {
interests
}
/>, document.getElementById("app"));
<script src="https://cdn.jsdelivr.net/npm/immutable#4.0.0-rc.12/dist/immutable.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0/umd/react-dom.production.min.js"></script>
<div id='app'></div>
The changes:
Rewrote your component as a class. This is purely to highlight the rest of the changes
Your component now takes an external prop containing interests. This means you can pass a Set and be sure that it won't have elements added out of the blue within the component
The component is in charge of the interest levels. As such, whenever you click on one of the buttons, the update function is called on the Set, which is used to update the items inside the collection
The entire thing is rendered as an array through render()
This is both more readable and easier to manage.
As you mentioned in this comment, you're looking for easier ways of updating objects in a deep object hierarchy using Immutable.js.
updateIn should do the trick.
const immutableObject = Immutable.fromJS({ outerProp: { innerCount: 1 } });
immutableObject.updateIn(['outerProp', 'innerCount'], count => count + 1);
It's also worth noting that you probably want to call Immutable.fromJS() instead of using Immutable.List() since the latter won't deeply convert your JavaScript object into an Immutable one, which can lead to bugs if you're assuming the data structure to be deeply Immutable. Switching the code above to use Immutable.fromJS() and updateIn we get:
// In updateRow
const immutableMyArray = fromJS(myArray);
const index = immutableMyArray.findIndex((item) => {
return item.id === id;
});
const newArray = immutableMyArray.updateIn([index, 'interestLevel'], interestLevel => interestLevel + 1);
setMyArray(newArray);

Add logic to the store?

I have a redux application with a "campaign" reducer/store.
Currently I have repeated code to check if a specific campaign is loaded or needs an API call to fetch details from the DB. Much simplified it looks like this:
// Reducer ----------
export default campaignReducer => (state, action) {
const campaignList = action.payload
return {
items: {... campaignList}
}
}
// Component ----------
const mapStateToProps = (state, ownProps) => {
const campaignId = ownProps.params.campaignId;
const campaign = state.campaign.items[campaignId] || {};
return {
needFetch: campaign.id
&& campaign.meta
&& (campaign.meta.loaded || campaign.meta.loading),
campaign,
};
}
export default connect(mapStateToProps)(TheComponent);
Now I don't like to repeat the complex condition for needFetch. I also don't like to have this complex code in the mapStateToProps function at all, I want to have a simple check. So I came up with this solution:
// Reducer NEW ----------
const needFetch = (items) => (id) => { // <-- Added this function.
if (!items[id]) return true;
if (!items[id].meta) return true;
if (!items[id].meta.loaded && !items[id].meta.loading) return true;
return false;
}
export default campaignReducer => (state, action) {
const campaignList = action.payload
return {
needFetch: needFetch(campaignList), // <-- Added public access to the new function.
items: {... campaignList}
}
}
// Component NEW ----------
const mapStateToProps = (state, ownProps) => {
const campaignId = ownProps.params.campaignId;
const campaign = state.campaign.items[campaignId] || {};
return {
needFetch: state.campaign.needFetch(campaignId), // <-- Much simpler!
campaign,
};
}
export default connect(mapStateToProps)(TheComponent);
Question: Is this a good solution, or does the redux-structure expect a different pattern to solve this?
Question 2: Should we add getter methods to the store, like store.campaign.getItem(myId) to add sanitation (make sure myId exists and is loaded, ..) or is there a different approach for this in redux?
Usually computational components should be responsible for doing this type of logic. Sure your function has a complex conditional check, it belongs exactly inside your computational component (just like the way you currently have it).
Also, redux is only for maintaining state. There's no reason to add methods to query values of the current state inside your reducers. A better way would be having a module specifically for parsing your state. You can then pass state to the module and it would extract the relevant info. Keep your redux/store code focused on computing a state only.
Your approach is somewhat against the idiomatic understanding of state in redux. You should keep only serializable data in the state, not functions. Otherwise you loose many of the benefits of redux, e.g. that you can very easily stash your application's state into the local storage or hydrate it from the server to resume previous sessions.
Instead, I would extract the condition into a separate library file and import it into the container component where necessary:
// needsFetch.js
export default function needsFetch(campaign) {
return campaign.id
&& campaign.meta
&& (campaign.meta.loaded || campaign.meta.loading);
}
// Component ----------
import needsFetch from './needsFetch';
const mapStateToProps = (state, ownProps) => {
const campaignId = ownProps.params.campaignId;
const campaign = state.campaign.items[campaignId] || {};
return {
needFetch: needsFetch(campaign),
campaign,
};
}
export default connect(mapStateToProps)(TheComponent);

Categories

Resources