I'm creating a packing list for a holiday.
I have these 2 cases:
switch(action.type) {
case 'ADD_ITEM': {
const items = [].concat(state.items).concat(action.data)
return { items }
}
case 'ITEM_ALREADY_ADDED': {
state.items.map((item) => (
item.name === action.data.name ? alert('Item already added') : item
))
return { ...state }
}
}
When I type into the input another item and then click add, it calls the second case statement (item already exists). tTis is working, however, I broke the add item case.
So I'm trying to do a ternary and I'm not sure what to put on the right-hand side of the : if the item isn't there. This is where I want to do my check and then call the other action.
So two questions:
How can I do this?
Is this the best place to be doing the ternary/check?
For your first question
you can do it like this
switch(action.type){
case 'ADD_ITEM': {
const items = [].concat(state.items).concat(action.data)
return { ...state, items }
}
// dont do the alert here, instead access the items array in your component using connect and mapStateToProps. do looing there and check whether it is already existing or not. depending on which dispatch action. you dont need to have ITEM_ALREADY_ADDED case.
}
// your component and for your second question
class A extends React.Component {
add = () => {
const {items} = this.props
// write yout logic here and check whether the item is already availle
//and accordingly dispatch the action
}
render() {
return (
<div>
<button onClick={this.add}>Add</button>
</div>
)
}
}
function mapStateToProps(state) {
items : state.yourreducrename.items,
}
Note : this is just higher level overview, please adjust the code
accordingly
Related
I have a list of items and I want to bring some data from a child component to the "source of truth", which items have been selected in this case, but the only way I can think of of specifying which list item has been selected is but using the event.target.id property. of which I specify in one of the child components. see below
constructor(props){
super(props)
this.state = {
showDialog : false,
preparedSpells: [],
}
}
onPrep(e){
let prepedSpells = this.state.preparedSpells
let targetSpell = Number(e.target.id)
if (prepedSpells.includes(targetSpell)){
let index = prepedSpells.indexOf(targetSpell)
prepedSpells.splice(index,1)
} else {
prepedSpells.push(targetSpell)
}
this.setState({
prepedSpells : prepedSpells
})
render(){
return(
<SpellList spells = {this.spells} onSpellClick = {this.onSpellClick} onClick = {this.onPrep}></SpellList>
);
I need the data to be at this level but I feel that there should be a way of setting the state in one of the list item components and then iterating through them all to find which are selected.
I know data only flows down in react but is there something im missing? or should I bottle up this feeling of wrongness
Looks like you're pretty close to correctness to me.
Let me tell you what I would change:
Instead of sending passing the whole event to "onPrep", you can just pass the id.
onPrep(targetSpell){
let prepedSpells = this.state.preparedSpells
if (prepedSpells.includes(targetSpell)){
let index = prepedSpells.indexOf(targetSpell)
prepedSpells.splice(index,1)
} else {
prepedSpells.push(targetSpell)
}
this.setState({
prepedSpells : prepedSpells
})
And I don't actually have the code for the SpellList component, but you should have something like this:
return (
<div>
{this.props.spells.map((spell) => {
return <button onClick={() => {this.props.onClick(spell.id)}}>{spell.name}</button>;
})}
</div>
);
That way you can pass whatever you want to the click event handler, and it makes things look cleaner.
I am trying to mutate state by running filter and map methods, when I am returning my array the state is becoming undefined.
const getAllApprovedProviderReducer = (state=defaultState, action) => {
if(action.type === GET_ALL_APPROVED_PROVIDER) {
return {
...state
}
}
if(action.type === FILTER_BY_FINANCIAL_SERVICES) {
console.log(action.payload)
return {
...state,
success: state.success.map((item, index)=> {
item.servicesOffered.filter(i=>{
if(i===action.payload) {
return item
}
else {
// console.log(state.success);
return item
}
})
})
}
}
return state;
}
At the presentation component the state is changed to undefied.
What I am doing wrong to make state undefined.
From my comment:
The function you pass to .map does not return anything, hence every value is mapped to undefined. return doesn't cross function boundaries and the function you pass to .filter should return a boolean value.
If you only want to keep the objects which offer a specific service, then you can do that the following way:
state.success.filter(item => item.servicesOffered.includes(action.payload))
.filter will remove every element for which the callback returns false.
Using .map here doesn't make sense since it will always be a 1:1 mapping between input and output. If you want to reduce the number of elements you have to use .filter.
And while it's possible to use .filter to test for the presence of a value in an array, there are more specific methods for that, such as .includes and .some.
I'm trying to make a refactoring of my reducer's, actions' code. The problem is that one state depends on another, so one state is updated and then another one must receive the same info but insted it updates it.
Here is the demo.
The folder is src/reducer.
I've tried to separate the states but it gave me more troubles.
F.e. I have two states: boards and currentBoard
What happens in boards:
case 'ADD_CARD':
const { list } = action.payload
return [
...boards.slice(0, list.boardId),
updateCards(currentBoard, action.payload),
...boards.slice(list.boardId + 1)
]
What happens in currentBoard:
case 'ADD_CARD':
return updateCards(currentBoard, action.payload)
How can I rewrite my code to make it better? I expect it at least to be updated once.
The action 'ADD_CARD' in boards already calls updateCards(currentBoard, action.payload), so just remove the ADD_CARD from the currentBoard.
Also you might want to change the code of ADD_CARD action inside boards (it's not clean):
case 'ADD_CARD':
const { list } = action.payload;
const newBoard = updateCards(currentBoard, action.payload);
list.splice(list.boardId, 0, newBoard);
return list;
}
I have an array of steps in my redux store. My my steps reducer manipulates the list of step by handling a series of actions:
ADD_STEP_REQUEST
REMOVE_STEP_REQUEST
MOVE_STEP_UP_REQUEST
MOVE_STEP_DOWN_REQUEST
So far so good.
I now have a requirement to perform a kind of validation every time there is a change to the order of the steps (or when a step is added or removed). During this validation I need to check each step to see if the step that precedes it fulfils certain criteria. I don't want to reject the changes if it doesn't. I just want to set an isInvalid flag on the step and ultimately change the way a step looks in the UI.
The simplest way I can handle this is adding a validateOrder() function (that applies the flags and returns the steps) that is run by the reducer at the end of each case statement:
case ADD_STEP_REQUEST: {
const amendedSteps = // add a step
return validateOrder(amendedSteps);
}
case REMOVE_STEP_REQUEST: {
const amendedSteps = // remove a step
return validateOrder(amendedSteps);
}
case MOVE_STEP_UP_REQUEST: {
const amendedSteps = // reorder steps
return validateOrder(amendedSteps);
}
case MOVE_STEP_DOWN_REQUEST: {
const amendedSteps = // reorder steps
return validateOrder(amendedSteps);
}
However, this feels wrong because I need to repeat the validateOrder call across all the case statements.
Is there a better way to handle this?
A reducer is just a function. You can wrap it with another function:
const yourReducer = (state = 'your initial state', action) => {
switch (action.type) {
case ADD_STEP_REQUEST:
// ...
return amendedSteps
case ...
}
}
const validatedReducer = (state, action) => {
switch (action.type) {
case ADD_STEP_REQUEST:
case REMOVE_STEP_REQUEST:
case MOVE_STEP_UP_REQUEST:
case MOVE_STEP_DOWN_REQUEST:
return validateOrder(yourReducer(state, action))
default:
return yourReducer(state, action)
}
}
Now you separate the responsibilities. The original reducer doesn't need to care about the validation. The validatedReducer will take care of that.
If the validateOrder must be applied to all the cases, then you don't need the switch statement in validatedReducer, so it will become:
const validatedReducer = (state, action) =>
validateOrder(yourReducer(state, action))
In my state I have showTab1, showTab2, showTab3. If tab i is selected, then the other tabs are set to false. So in my render function I want to be able to return something like this:
return (
<div>
{
(() => {
if (someCondition) {
if (this.state.showTab1) {
return (
<div>
<Tab1/>
</div>
)
} else if (this.state.showTab2) {
return (
<div>
<Tab2/>
</div>
)
} else if (this.state.showTab3) {
return (
<div>
<Tab3/>
</div>
)
}
}
return <span />;
})()
}
<AnotherComponent>
</div>
);
But I know that it's not allowed to have multiple returns, or at least it's considered bad practice. How can I get around this?
Ideally you'd use a single variable to determine which tab you need and then you could use a switch statement instead of multiple ifs. Something like this would be nice:
render() {
const { currentTabId } = this.props;
let CurrentTab;
switch( currentTabId ) {
case 'tab1':
CurrentTab = Tab1;
break;
case 'tab2':
CurrentTab = Tab2;
break;
case 'tab3':
CurrentTab = Tab3;
break;
default:
CurrentTab = null;
};
return <div>{ CurrentTab && <CurrentTab /> }</div>;
}
or
render() {
const { currentTabId } = this.props;
const tabs = {
tab1: Tab1,
tab2: Tab2,
tab3: Tab3,
};
const CurrentTab = tabs[ currentTabId ] || null;
return <div>{ CurrentTab && <CurrentTab /> }</div>;
}
What do your tabs have in common, and what makes one different from the other ones?
Having that in mind create a single Tab component that takes the needed properties (differences) and renders accordingly. But ... why?
React is awesome because it can know the minimal amount of changes needed to the DOM in order to represent the state you've set, allowing you to handle your entire UI as a state-based system. The good performance of React is based on how well it can tell those differences.
If your render method returns an entirely different component each time, React will remove one and append the other because it can't tell the difference between one and the other. But if you specify with properties and the state what changes and what doesn't change in your component, then it'll do its job very well. The DOM is awful, it has always been, the less changes you make to it, the better your page will behave.
Does that mean you can't return three entirely different components? Of course not, but if you would do that on all of your components, then you would have a very serious performance issue. That sounds pretty much like a bad practice and good enough reason to avoid it.
Take SO tabs at the right as a very trivial example, and supose you navigate to them from other place in the page, therefore only the one active is shown.
If you do this:
<div>
{() => {
if (this.state.active === 'jobs')
return <Jobs>;
if (this.state.active === 'doc')
return <Documentation>;
// ... you get it
}}
</div>
Whenever you change state.active React will remove the tab, and append a new one.
But if you don't use a bad practice, and use a good practice like stateless functional components
const Tab = ({text, href, children}) => (
<div>
<a href={href}>{text}</a>
{children}
</div>
);
// In the parent one
textByTab() {
switch(this.state.active) {
case 'jobs':
return 'Jobs';
case 'doc':
return 'Documentation';
}
}
hrefByTab() { // ... you get it }
childrenByTab() {
if (this.state.active === 'doc')
return <span className="beta-thing">Beta</span>;
}
render() {
return (
<div>
<Tab text={this.textByTab()} href={this.hrefByTab()}>
{this.childrenByTab()}
</Tab>
</div>
);
}
Now React knows exactly what can change, how it can change, and can even do fancy functional stuff for even better performance.