Redux Recipe List
I made a simple React + Redux app which allows you to add and remove recipes to a list.
Here are the user stories the app fulfills:
User Story: I can create recipes that have names and ingredients.
User Story: I can see an index view where the names of all the recipes are visible.
User Story: I can click into any of those recipes to view it.
User Story: I can delete these recipes.
User Story: All new recipes I add are saved in my browser's local storage. If I refresh the page, these recipes will still be there.
Code:
import React from 'react'
import ReactDOM from 'react-dom'
import { connect } from 'react-redux'
import { createStore } from 'redux'
const ADD_RECIPE = 'ADD_RECIPE'
const SHOW_RECIPE = 'SHOW_RECIPE'
const EDIT_RECIPE = 'EDIT_RECIPE'
const UPDATE_ACTIVE_NAME = 'UPDATE_ACTIVE_NAME'
const UPDATE_ACTIVE_INGREDIENTS = 'UPDATE_ACTIVE_INGREDIENTS'
const TOGGLE_ACTIVE_ID = 'TOGGLE_ACTIVE_ID'
const DELETE_RECIPE = 'DELETE_RECIPE'
const initialState = {
activeName: '',
activeIngredients: '',
activeId: '',
recipes: [
{
id: 1,
name: 'Pizza',
ingredients: 'Dough, tomato, cheese, salt, pepper, olives, mushrooms, ham.'
},
{
id: 2,
name: 'Sherpherd's Pie',
ingredients: 'Potato, lamb'
},
{
id: 3,
name: 'Huel Shake',
ingredients: 'Milk, huel'
}
]
}
// action creators
const saveStoreRecipesToLocalStorage = (state) => {
localStorage.setItem('state', JSON.stringify(
{...state, activeId: ""}
));
}
const getStoreRecipesFromLocalStorage = () => {
const state = JSON.parse(localStorage.getItem('state'))
return state.recipes.length && state
}
const addRecipe = (id, name, ingredients) => {
return {
type: ADD_RECIPE,
payload: {
id,
name,
ingredients
}
}
}
const updateActiveName = payload => {
return {
type: UPDATE_ACTIVE_NAME,
payload
}
}
const updateActiveIngredients = payload => {
return {
type: UPDATE_ACTIVE_INGREDIENTS,
payload
}
}
const toggleActiveId = id => {
return {
type: TOGGLE_ACTIVE_ID,
payload: id
}
}
const deleteRecipe = id => {
return {
type: DELETE_RECIPE,
payload: id
}
}
// reducer
function recipes (state = getStoreRecipesFromLocalStorage() || initialState, action) {
console.warn('action:', action)
switch (action.type) {
case TOGGLE_ACTIVE_ID:
return {...state, activeId: action.payload}
case UPDATE_ACTIVE_NAME:
return {...state, activeName: action.payload}
case UPDATE_ACTIVE_INGREDIENTS:
return {...state, activeIngredients: action.payload}
case ADD_RECIPE:
return {...state, recipes: [...state.recipes, action.payload]}
case DELETE_RECIPE:
return {...state, recipes: state.recipes.filter(recipe => recipe.id !== action.payload)}
case EDIT_RECIPE:
return
default:
return state
}
}
const Recipes = (props) => {
getStoreRecipesFromLocalStorage()
const {addRecipe, updateActiveIngredients, updateActiveName, toggleActiveId, deleteRecipe, state} = props
const id = new Date().getTime() / 1000
return (
<div style={{width: 320}}>
<form onSubmit={e => {
e.preventDefault()
addRecipe(id, state.activeName, state.activeIngredients)
}}>
<input
type="text"
placeholder="Name"
value={state.activeName}
onChange={(e) => updateActiveName(e.target.value)}
/>
<input
placeholder="Ingredients"
value={state.activeIngredients}
onChange={e => updateActiveIngredients(e.target.value)}
/>
<button type="submit">Add Recipe</button>
</form>
{state.recipes.map(item => <ul key={item.name}>
<div style={{display: 'flex', justifyContent: 'space-between'}}>
<li onClick={() => toggleActiveId(item.id)}>
<div>{item.name}</div>
{state.activeId === item.id && <div>{item.ingredients}</div>}
</li>
<div style={{color: 'red'}} onClick={() => deleteRecipe(item.id)}>×</div>
</div>
</ul>)}
</div>
)
}
const mapStateToProps = (state) => {
return {
state
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
addRecipe: (id, name, ingredients) => {
if (name && ingredients) {
dispatch(addRecipe(id, name, ingredients))
dispatch(updateActiveName(''))
dispatch(updateActiveIngredients(''))
saveStoreRecipesToLocalStorage(ownProps.store.getState())
}
},
updateActiveName: name => dispatch(updateActiveName(name)),
updateActiveIngredients: ingredients => dispatch(updateActiveIngredients(ingredients)),
toggleActiveId: id => dispatch(toggleActiveId(id)),
deleteRecipe: id => {
dispatch(deleteRecipe(id))
saveStoreRecipesToLocalStorage(ownProps.store.getState())
}
}
}
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Recipes)
const store = createStore(recipes, window.__REDUX_DEVTOOLS_EXTENSION__())
const render = () => ReactDOM.render(<App store={store}/>, document.getElementById('root'))
store.subscribe(render)
render()
I would like to structure the app better by splitting this file into separate files but for the sake of this question I have kept it in one file.
Demo (adapted for codepen without localStorage features): https://codepen.io/alanbuchanan/pen/LQWKBZ
Making this small app brought about some questions I have about Redux:
I have an
updateActiveName
andupdateActiveIngredients
function because there are only these two fields that get added by the form. Would it be better instead to use anupdateActiveObject
and run this when either field changes? I see this as a benefit for when other fields get added, but less efficient because I will be running an onChange function for several fields that aren't changing.The naming of my action creators, reducers, and functions inside
mapDispatchToProps
are usually the same. For exampleUPDATE_ACTIVE_INGREDIENTS
,updateActiveIngredients
action creator andupdateActiveIngredients
function. Is there a better approach to this?In the
addRecipe
function insidemapDispatchToProps
, I am validating thename
andingredients
values. Should this go inside this function, or somewhere else?In the
addRecipe
function insidemapDispatchToProps
, I am dispatching many actions. Is this correct practise?
Any other feedback for improving my Redux is most welcome.
react.js jsx redux
add a comment |
I made a simple React + Redux app which allows you to add and remove recipes to a list.
Here are the user stories the app fulfills:
User Story: I can create recipes that have names and ingredients.
User Story: I can see an index view where the names of all the recipes are visible.
User Story: I can click into any of those recipes to view it.
User Story: I can delete these recipes.
User Story: All new recipes I add are saved in my browser's local storage. If I refresh the page, these recipes will still be there.
Code:
import React from 'react'
import ReactDOM from 'react-dom'
import { connect } from 'react-redux'
import { createStore } from 'redux'
const ADD_RECIPE = 'ADD_RECIPE'
const SHOW_RECIPE = 'SHOW_RECIPE'
const EDIT_RECIPE = 'EDIT_RECIPE'
const UPDATE_ACTIVE_NAME = 'UPDATE_ACTIVE_NAME'
const UPDATE_ACTIVE_INGREDIENTS = 'UPDATE_ACTIVE_INGREDIENTS'
const TOGGLE_ACTIVE_ID = 'TOGGLE_ACTIVE_ID'
const DELETE_RECIPE = 'DELETE_RECIPE'
const initialState = {
activeName: '',
activeIngredients: '',
activeId: '',
recipes: [
{
id: 1,
name: 'Pizza',
ingredients: 'Dough, tomato, cheese, salt, pepper, olives, mushrooms, ham.'
},
{
id: 2,
name: 'Sherpherd's Pie',
ingredients: 'Potato, lamb'
},
{
id: 3,
name: 'Huel Shake',
ingredients: 'Milk, huel'
}
]
}
// action creators
const saveStoreRecipesToLocalStorage = (state) => {
localStorage.setItem('state', JSON.stringify(
{...state, activeId: ""}
));
}
const getStoreRecipesFromLocalStorage = () => {
const state = JSON.parse(localStorage.getItem('state'))
return state.recipes.length && state
}
const addRecipe = (id, name, ingredients) => {
return {
type: ADD_RECIPE,
payload: {
id,
name,
ingredients
}
}
}
const updateActiveName = payload => {
return {
type: UPDATE_ACTIVE_NAME,
payload
}
}
const updateActiveIngredients = payload => {
return {
type: UPDATE_ACTIVE_INGREDIENTS,
payload
}
}
const toggleActiveId = id => {
return {
type: TOGGLE_ACTIVE_ID,
payload: id
}
}
const deleteRecipe = id => {
return {
type: DELETE_RECIPE,
payload: id
}
}
// reducer
function recipes (state = getStoreRecipesFromLocalStorage() || initialState, action) {
console.warn('action:', action)
switch (action.type) {
case TOGGLE_ACTIVE_ID:
return {...state, activeId: action.payload}
case UPDATE_ACTIVE_NAME:
return {...state, activeName: action.payload}
case UPDATE_ACTIVE_INGREDIENTS:
return {...state, activeIngredients: action.payload}
case ADD_RECIPE:
return {...state, recipes: [...state.recipes, action.payload]}
case DELETE_RECIPE:
return {...state, recipes: state.recipes.filter(recipe => recipe.id !== action.payload)}
case EDIT_RECIPE:
return
default:
return state
}
}
const Recipes = (props) => {
getStoreRecipesFromLocalStorage()
const {addRecipe, updateActiveIngredients, updateActiveName, toggleActiveId, deleteRecipe, state} = props
const id = new Date().getTime() / 1000
return (
<div style={{width: 320}}>
<form onSubmit={e => {
e.preventDefault()
addRecipe(id, state.activeName, state.activeIngredients)
}}>
<input
type="text"
placeholder="Name"
value={state.activeName}
onChange={(e) => updateActiveName(e.target.value)}
/>
<input
placeholder="Ingredients"
value={state.activeIngredients}
onChange={e => updateActiveIngredients(e.target.value)}
/>
<button type="submit">Add Recipe</button>
</form>
{state.recipes.map(item => <ul key={item.name}>
<div style={{display: 'flex', justifyContent: 'space-between'}}>
<li onClick={() => toggleActiveId(item.id)}>
<div>{item.name}</div>
{state.activeId === item.id && <div>{item.ingredients}</div>}
</li>
<div style={{color: 'red'}} onClick={() => deleteRecipe(item.id)}>×</div>
</div>
</ul>)}
</div>
)
}
const mapStateToProps = (state) => {
return {
state
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
addRecipe: (id, name, ingredients) => {
if (name && ingredients) {
dispatch(addRecipe(id, name, ingredients))
dispatch(updateActiveName(''))
dispatch(updateActiveIngredients(''))
saveStoreRecipesToLocalStorage(ownProps.store.getState())
}
},
updateActiveName: name => dispatch(updateActiveName(name)),
updateActiveIngredients: ingredients => dispatch(updateActiveIngredients(ingredients)),
toggleActiveId: id => dispatch(toggleActiveId(id)),
deleteRecipe: id => {
dispatch(deleteRecipe(id))
saveStoreRecipesToLocalStorage(ownProps.store.getState())
}
}
}
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Recipes)
const store = createStore(recipes, window.__REDUX_DEVTOOLS_EXTENSION__())
const render = () => ReactDOM.render(<App store={store}/>, document.getElementById('root'))
store.subscribe(render)
render()
I would like to structure the app better by splitting this file into separate files but for the sake of this question I have kept it in one file.
Demo (adapted for codepen without localStorage features): https://codepen.io/alanbuchanan/pen/LQWKBZ
Making this small app brought about some questions I have about Redux:
I have an
updateActiveName
andupdateActiveIngredients
function because there are only these two fields that get added by the form. Would it be better instead to use anupdateActiveObject
and run this when either field changes? I see this as a benefit for when other fields get added, but less efficient because I will be running an onChange function for several fields that aren't changing.The naming of my action creators, reducers, and functions inside
mapDispatchToProps
are usually the same. For exampleUPDATE_ACTIVE_INGREDIENTS
,updateActiveIngredients
action creator andupdateActiveIngredients
function. Is there a better approach to this?In the
addRecipe
function insidemapDispatchToProps
, I am validating thename
andingredients
values. Should this go inside this function, or somewhere else?In the
addRecipe
function insidemapDispatchToProps
, I am dispatching many actions. Is this correct practise?
Any other feedback for improving my Redux is most welcome.
react.js jsx redux
add a comment |
I made a simple React + Redux app which allows you to add and remove recipes to a list.
Here are the user stories the app fulfills:
User Story: I can create recipes that have names and ingredients.
User Story: I can see an index view where the names of all the recipes are visible.
User Story: I can click into any of those recipes to view it.
User Story: I can delete these recipes.
User Story: All new recipes I add are saved in my browser's local storage. If I refresh the page, these recipes will still be there.
Code:
import React from 'react'
import ReactDOM from 'react-dom'
import { connect } from 'react-redux'
import { createStore } from 'redux'
const ADD_RECIPE = 'ADD_RECIPE'
const SHOW_RECIPE = 'SHOW_RECIPE'
const EDIT_RECIPE = 'EDIT_RECIPE'
const UPDATE_ACTIVE_NAME = 'UPDATE_ACTIVE_NAME'
const UPDATE_ACTIVE_INGREDIENTS = 'UPDATE_ACTIVE_INGREDIENTS'
const TOGGLE_ACTIVE_ID = 'TOGGLE_ACTIVE_ID'
const DELETE_RECIPE = 'DELETE_RECIPE'
const initialState = {
activeName: '',
activeIngredients: '',
activeId: '',
recipes: [
{
id: 1,
name: 'Pizza',
ingredients: 'Dough, tomato, cheese, salt, pepper, olives, mushrooms, ham.'
},
{
id: 2,
name: 'Sherpherd's Pie',
ingredients: 'Potato, lamb'
},
{
id: 3,
name: 'Huel Shake',
ingredients: 'Milk, huel'
}
]
}
// action creators
const saveStoreRecipesToLocalStorage = (state) => {
localStorage.setItem('state', JSON.stringify(
{...state, activeId: ""}
));
}
const getStoreRecipesFromLocalStorage = () => {
const state = JSON.parse(localStorage.getItem('state'))
return state.recipes.length && state
}
const addRecipe = (id, name, ingredients) => {
return {
type: ADD_RECIPE,
payload: {
id,
name,
ingredients
}
}
}
const updateActiveName = payload => {
return {
type: UPDATE_ACTIVE_NAME,
payload
}
}
const updateActiveIngredients = payload => {
return {
type: UPDATE_ACTIVE_INGREDIENTS,
payload
}
}
const toggleActiveId = id => {
return {
type: TOGGLE_ACTIVE_ID,
payload: id
}
}
const deleteRecipe = id => {
return {
type: DELETE_RECIPE,
payload: id
}
}
// reducer
function recipes (state = getStoreRecipesFromLocalStorage() || initialState, action) {
console.warn('action:', action)
switch (action.type) {
case TOGGLE_ACTIVE_ID:
return {...state, activeId: action.payload}
case UPDATE_ACTIVE_NAME:
return {...state, activeName: action.payload}
case UPDATE_ACTIVE_INGREDIENTS:
return {...state, activeIngredients: action.payload}
case ADD_RECIPE:
return {...state, recipes: [...state.recipes, action.payload]}
case DELETE_RECIPE:
return {...state, recipes: state.recipes.filter(recipe => recipe.id !== action.payload)}
case EDIT_RECIPE:
return
default:
return state
}
}
const Recipes = (props) => {
getStoreRecipesFromLocalStorage()
const {addRecipe, updateActiveIngredients, updateActiveName, toggleActiveId, deleteRecipe, state} = props
const id = new Date().getTime() / 1000
return (
<div style={{width: 320}}>
<form onSubmit={e => {
e.preventDefault()
addRecipe(id, state.activeName, state.activeIngredients)
}}>
<input
type="text"
placeholder="Name"
value={state.activeName}
onChange={(e) => updateActiveName(e.target.value)}
/>
<input
placeholder="Ingredients"
value={state.activeIngredients}
onChange={e => updateActiveIngredients(e.target.value)}
/>
<button type="submit">Add Recipe</button>
</form>
{state.recipes.map(item => <ul key={item.name}>
<div style={{display: 'flex', justifyContent: 'space-between'}}>
<li onClick={() => toggleActiveId(item.id)}>
<div>{item.name}</div>
{state.activeId === item.id && <div>{item.ingredients}</div>}
</li>
<div style={{color: 'red'}} onClick={() => deleteRecipe(item.id)}>×</div>
</div>
</ul>)}
</div>
)
}
const mapStateToProps = (state) => {
return {
state
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
addRecipe: (id, name, ingredients) => {
if (name && ingredients) {
dispatch(addRecipe(id, name, ingredients))
dispatch(updateActiveName(''))
dispatch(updateActiveIngredients(''))
saveStoreRecipesToLocalStorage(ownProps.store.getState())
}
},
updateActiveName: name => dispatch(updateActiveName(name)),
updateActiveIngredients: ingredients => dispatch(updateActiveIngredients(ingredients)),
toggleActiveId: id => dispatch(toggleActiveId(id)),
deleteRecipe: id => {
dispatch(deleteRecipe(id))
saveStoreRecipesToLocalStorage(ownProps.store.getState())
}
}
}
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Recipes)
const store = createStore(recipes, window.__REDUX_DEVTOOLS_EXTENSION__())
const render = () => ReactDOM.render(<App store={store}/>, document.getElementById('root'))
store.subscribe(render)
render()
I would like to structure the app better by splitting this file into separate files but for the sake of this question I have kept it in one file.
Demo (adapted for codepen without localStorage features): https://codepen.io/alanbuchanan/pen/LQWKBZ
Making this small app brought about some questions I have about Redux:
I have an
updateActiveName
andupdateActiveIngredients
function because there are only these two fields that get added by the form. Would it be better instead to use anupdateActiveObject
and run this when either field changes? I see this as a benefit for when other fields get added, but less efficient because I will be running an onChange function for several fields that aren't changing.The naming of my action creators, reducers, and functions inside
mapDispatchToProps
are usually the same. For exampleUPDATE_ACTIVE_INGREDIENTS
,updateActiveIngredients
action creator andupdateActiveIngredients
function. Is there a better approach to this?In the
addRecipe
function insidemapDispatchToProps
, I am validating thename
andingredients
values. Should this go inside this function, or somewhere else?In the
addRecipe
function insidemapDispatchToProps
, I am dispatching many actions. Is this correct practise?
Any other feedback for improving my Redux is most welcome.
react.js jsx redux
I made a simple React + Redux app which allows you to add and remove recipes to a list.
Here are the user stories the app fulfills:
User Story: I can create recipes that have names and ingredients.
User Story: I can see an index view where the names of all the recipes are visible.
User Story: I can click into any of those recipes to view it.
User Story: I can delete these recipes.
User Story: All new recipes I add are saved in my browser's local storage. If I refresh the page, these recipes will still be there.
Code:
import React from 'react'
import ReactDOM from 'react-dom'
import { connect } from 'react-redux'
import { createStore } from 'redux'
const ADD_RECIPE = 'ADD_RECIPE'
const SHOW_RECIPE = 'SHOW_RECIPE'
const EDIT_RECIPE = 'EDIT_RECIPE'
const UPDATE_ACTIVE_NAME = 'UPDATE_ACTIVE_NAME'
const UPDATE_ACTIVE_INGREDIENTS = 'UPDATE_ACTIVE_INGREDIENTS'
const TOGGLE_ACTIVE_ID = 'TOGGLE_ACTIVE_ID'
const DELETE_RECIPE = 'DELETE_RECIPE'
const initialState = {
activeName: '',
activeIngredients: '',
activeId: '',
recipes: [
{
id: 1,
name: 'Pizza',
ingredients: 'Dough, tomato, cheese, salt, pepper, olives, mushrooms, ham.'
},
{
id: 2,
name: 'Sherpherd's Pie',
ingredients: 'Potato, lamb'
},
{
id: 3,
name: 'Huel Shake',
ingredients: 'Milk, huel'
}
]
}
// action creators
const saveStoreRecipesToLocalStorage = (state) => {
localStorage.setItem('state', JSON.stringify(
{...state, activeId: ""}
));
}
const getStoreRecipesFromLocalStorage = () => {
const state = JSON.parse(localStorage.getItem('state'))
return state.recipes.length && state
}
const addRecipe = (id, name, ingredients) => {
return {
type: ADD_RECIPE,
payload: {
id,
name,
ingredients
}
}
}
const updateActiveName = payload => {
return {
type: UPDATE_ACTIVE_NAME,
payload
}
}
const updateActiveIngredients = payload => {
return {
type: UPDATE_ACTIVE_INGREDIENTS,
payload
}
}
const toggleActiveId = id => {
return {
type: TOGGLE_ACTIVE_ID,
payload: id
}
}
const deleteRecipe = id => {
return {
type: DELETE_RECIPE,
payload: id
}
}
// reducer
function recipes (state = getStoreRecipesFromLocalStorage() || initialState, action) {
console.warn('action:', action)
switch (action.type) {
case TOGGLE_ACTIVE_ID:
return {...state, activeId: action.payload}
case UPDATE_ACTIVE_NAME:
return {...state, activeName: action.payload}
case UPDATE_ACTIVE_INGREDIENTS:
return {...state, activeIngredients: action.payload}
case ADD_RECIPE:
return {...state, recipes: [...state.recipes, action.payload]}
case DELETE_RECIPE:
return {...state, recipes: state.recipes.filter(recipe => recipe.id !== action.payload)}
case EDIT_RECIPE:
return
default:
return state
}
}
const Recipes = (props) => {
getStoreRecipesFromLocalStorage()
const {addRecipe, updateActiveIngredients, updateActiveName, toggleActiveId, deleteRecipe, state} = props
const id = new Date().getTime() / 1000
return (
<div style={{width: 320}}>
<form onSubmit={e => {
e.preventDefault()
addRecipe(id, state.activeName, state.activeIngredients)
}}>
<input
type="text"
placeholder="Name"
value={state.activeName}
onChange={(e) => updateActiveName(e.target.value)}
/>
<input
placeholder="Ingredients"
value={state.activeIngredients}
onChange={e => updateActiveIngredients(e.target.value)}
/>
<button type="submit">Add Recipe</button>
</form>
{state.recipes.map(item => <ul key={item.name}>
<div style={{display: 'flex', justifyContent: 'space-between'}}>
<li onClick={() => toggleActiveId(item.id)}>
<div>{item.name}</div>
{state.activeId === item.id && <div>{item.ingredients}</div>}
</li>
<div style={{color: 'red'}} onClick={() => deleteRecipe(item.id)}>×</div>
</div>
</ul>)}
</div>
)
}
const mapStateToProps = (state) => {
return {
state
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
addRecipe: (id, name, ingredients) => {
if (name && ingredients) {
dispatch(addRecipe(id, name, ingredients))
dispatch(updateActiveName(''))
dispatch(updateActiveIngredients(''))
saveStoreRecipesToLocalStorage(ownProps.store.getState())
}
},
updateActiveName: name => dispatch(updateActiveName(name)),
updateActiveIngredients: ingredients => dispatch(updateActiveIngredients(ingredients)),
toggleActiveId: id => dispatch(toggleActiveId(id)),
deleteRecipe: id => {
dispatch(deleteRecipe(id))
saveStoreRecipesToLocalStorage(ownProps.store.getState())
}
}
}
const App = connect(
mapStateToProps,
mapDispatchToProps
)(Recipes)
const store = createStore(recipes, window.__REDUX_DEVTOOLS_EXTENSION__())
const render = () => ReactDOM.render(<App store={store}/>, document.getElementById('root'))
store.subscribe(render)
render()
I would like to structure the app better by splitting this file into separate files but for the sake of this question I have kept it in one file.
Demo (adapted for codepen without localStorage features): https://codepen.io/alanbuchanan/pen/LQWKBZ
Making this small app brought about some questions I have about Redux:
I have an
updateActiveName
andupdateActiveIngredients
function because there are only these two fields that get added by the form. Would it be better instead to use anupdateActiveObject
and run this when either field changes? I see this as a benefit for when other fields get added, but less efficient because I will be running an onChange function for several fields that aren't changing.The naming of my action creators, reducers, and functions inside
mapDispatchToProps
are usually the same. For exampleUPDATE_ACTIVE_INGREDIENTS
,updateActiveIngredients
action creator andupdateActiveIngredients
function. Is there a better approach to this?In the
addRecipe
function insidemapDispatchToProps
, I am validating thename
andingredients
values. Should this go inside this function, or somewhere else?In the
addRecipe
function insidemapDispatchToProps
, I am dispatching many actions. Is this correct practise?
Any other feedback for improving my Redux is most welcome.
react.js jsx redux
react.js jsx redux
edited Feb 10 at 20:01
200_success
128k15150412
128k15150412
asked Feb 10 at 19:55
alanbuchanan
66721325
66721325
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
I've only started working with React/Redux myself, but here are my thoughts.
In my system I am saving the form values to local component state until the form is saved. I have one onChange handler method which checks which form field it is and then will setState.
This is something I also find with React/Redux - a lot of boilerplate and repetition to update a few pieces of data... Similar to yourself I have the Action Types / Action Creators / API Calls / Reducers / Component methods all with very similar names. I think that is just the way it is. I'm also looking to include redux-saga in our code so that potentially means even more of the same named methods.
I use parsley for form validating, so that code is handled by the library. If I really needed to do further checking, I would do it in the onChange before changing the local component state.
You have everything in one component at the moment for listing the recipes and adding the recipes. I would split them into separate components and then once you're finished with saving a new recipe, render the list again. Your app is very much like a To Do list app, have a look for examples of that.
If it wasn't for the speed of react-redux, I probably wouldn't be bothered with it!
Here's an example of a to do app that has a similar structure to my own app (which I hope is well structured lol).
https://github.com/reactjs/redux/tree/master/examples/todomvc
add a comment |
The one thing that I would change is that since you are already using react-redux
library, you might as well do this -
let store = createStore(recipes, window.__REDUX_DEVTOOLS_EXTENSION__())
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
So that you don't have to manually subscribe
like so -
store.subscribe(render)
render()
About the questions that you mentioned -
- I have an
updateActiveName
andupdateActiveIngredients
function because there are only these two fields that get added by the form. Would it be better instead to use anupdateActiveObject
and run this when either field changes? I see this as a benefit for when other fields get added, but less efficient because I will be running anonChange
function for several fields that aren't changing.
This is again an opinion, but I wouldn't do that. One reason is that they don't/might not have similar types.
An example where this might work is - say - you have a big form where you have to enter a number of dates, one for birthdate, one for date of joining the company etc. You could a function changeDate()/updateDate()
which would serve as a common function to update dates.
- The naming of my action creators, reducers, and functions inside
mapDispatchToProps
are usually the same. For exampleUPDATE_ACTIVE_INGREDIENTS
,updateActiveIngredients
action creator andupdateActiveIngredients
function. Is there a better approach to this?
This is fine, so long as you stick with it. You anyways don't have to worry about the ActionCreator
names because, if you access the methods/functions that are defined in mapDispatchToProps()
and don't directly dispatch()
actions in the Component
itself, you wouldn't find much problems with understanding what goes where.
What I mean is, if you use - this.props.updateActiveIngredients()
- then it is fine. However, if you use - this.props.dispatch(updateActiveIngredients())
you might get confused.
3.In the
addRecipe
function insidemapDispatchToProps
, I am validating thename
andingredients
values. Should this go inside this function, or somewhere else?
Technically, it is a function. You can move it to a named function, but for something this small this is alright.
4.In the
addRecipe
function insidemapDispatchToProps
, I am dispatching many actions. Is this correct practice?
This is correct. You can also add a redux-thunk
middleware and add an ActionCreator
that returns a function instead of a simple JSON object.
For e.g.:
const mapDispatchToProps = (dispatch, ownProps) => {
return {
addRecipe: (id, name, ingredients) => {
if (name && ingredients) {
dispatch(addRecipe(id, name, ingredients))
dispatch(updateActiveName(''))
dispatch(updateActiveIngredients(''))
saveStoreRecipesToLocalStorage(ownProps.store.getState())
}
}
}
}
could be replaced by something like this -
const mapDispatchToProps = (dispatch, ownProps) => {
return {
addRecipe: (id, name, ingredients) => dispatch(addRecipe(id, name, ingredients))
}
}
// Action Creator
addRecipe(id, name, ingredients) => {
return (dispatch, getState) => {
if (name && ingredients) {
dispatch(addRecipe(id, name, ingredients))
dispatch(updateActiveName(''))
dispatch(updateActiveIngredients(''))
// Get the proper state that you want using getState()
const something = getState()
saveStoreRecipesToLocalStorage(something)
}
}
}
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f187274%2fredux-recipe-list%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
I've only started working with React/Redux myself, but here are my thoughts.
In my system I am saving the form values to local component state until the form is saved. I have one onChange handler method which checks which form field it is and then will setState.
This is something I also find with React/Redux - a lot of boilerplate and repetition to update a few pieces of data... Similar to yourself I have the Action Types / Action Creators / API Calls / Reducers / Component methods all with very similar names. I think that is just the way it is. I'm also looking to include redux-saga in our code so that potentially means even more of the same named methods.
I use parsley for form validating, so that code is handled by the library. If I really needed to do further checking, I would do it in the onChange before changing the local component state.
You have everything in one component at the moment for listing the recipes and adding the recipes. I would split them into separate components and then once you're finished with saving a new recipe, render the list again. Your app is very much like a To Do list app, have a look for examples of that.
If it wasn't for the speed of react-redux, I probably wouldn't be bothered with it!
Here's an example of a to do app that has a similar structure to my own app (which I hope is well structured lol).
https://github.com/reactjs/redux/tree/master/examples/todomvc
add a comment |
I've only started working with React/Redux myself, but here are my thoughts.
In my system I am saving the form values to local component state until the form is saved. I have one onChange handler method which checks which form field it is and then will setState.
This is something I also find with React/Redux - a lot of boilerplate and repetition to update a few pieces of data... Similar to yourself I have the Action Types / Action Creators / API Calls / Reducers / Component methods all with very similar names. I think that is just the way it is. I'm also looking to include redux-saga in our code so that potentially means even more of the same named methods.
I use parsley for form validating, so that code is handled by the library. If I really needed to do further checking, I would do it in the onChange before changing the local component state.
You have everything in one component at the moment for listing the recipes and adding the recipes. I would split them into separate components and then once you're finished with saving a new recipe, render the list again. Your app is very much like a To Do list app, have a look for examples of that.
If it wasn't for the speed of react-redux, I probably wouldn't be bothered with it!
Here's an example of a to do app that has a similar structure to my own app (which I hope is well structured lol).
https://github.com/reactjs/redux/tree/master/examples/todomvc
add a comment |
I've only started working with React/Redux myself, but here are my thoughts.
In my system I am saving the form values to local component state until the form is saved. I have one onChange handler method which checks which form field it is and then will setState.
This is something I also find with React/Redux - a lot of boilerplate and repetition to update a few pieces of data... Similar to yourself I have the Action Types / Action Creators / API Calls / Reducers / Component methods all with very similar names. I think that is just the way it is. I'm also looking to include redux-saga in our code so that potentially means even more of the same named methods.
I use parsley for form validating, so that code is handled by the library. If I really needed to do further checking, I would do it in the onChange before changing the local component state.
You have everything in one component at the moment for listing the recipes and adding the recipes. I would split them into separate components and then once you're finished with saving a new recipe, render the list again. Your app is very much like a To Do list app, have a look for examples of that.
If it wasn't for the speed of react-redux, I probably wouldn't be bothered with it!
Here's an example of a to do app that has a similar structure to my own app (which I hope is well structured lol).
https://github.com/reactjs/redux/tree/master/examples/todomvc
I've only started working with React/Redux myself, but here are my thoughts.
In my system I am saving the form values to local component state until the form is saved. I have one onChange handler method which checks which form field it is and then will setState.
This is something I also find with React/Redux - a lot of boilerplate and repetition to update a few pieces of data... Similar to yourself I have the Action Types / Action Creators / API Calls / Reducers / Component methods all with very similar names. I think that is just the way it is. I'm also looking to include redux-saga in our code so that potentially means even more of the same named methods.
I use parsley for form validating, so that code is handled by the library. If I really needed to do further checking, I would do it in the onChange before changing the local component state.
You have everything in one component at the moment for listing the recipes and adding the recipes. I would split them into separate components and then once you're finished with saving a new recipe, render the list again. Your app is very much like a To Do list app, have a look for examples of that.
If it wasn't for the speed of react-redux, I probably wouldn't be bothered with it!
Here's an example of a to do app that has a similar structure to my own app (which I hope is well structured lol).
https://github.com/reactjs/redux/tree/master/examples/todomvc
edited Feb 11 at 12:48
answered Feb 11 at 12:41
DavidC799
12
12
add a comment |
add a comment |
The one thing that I would change is that since you are already using react-redux
library, you might as well do this -
let store = createStore(recipes, window.__REDUX_DEVTOOLS_EXTENSION__())
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
So that you don't have to manually subscribe
like so -
store.subscribe(render)
render()
About the questions that you mentioned -
- I have an
updateActiveName
andupdateActiveIngredients
function because there are only these two fields that get added by the form. Would it be better instead to use anupdateActiveObject
and run this when either field changes? I see this as a benefit for when other fields get added, but less efficient because I will be running anonChange
function for several fields that aren't changing.
This is again an opinion, but I wouldn't do that. One reason is that they don't/might not have similar types.
An example where this might work is - say - you have a big form where you have to enter a number of dates, one for birthdate, one for date of joining the company etc. You could a function changeDate()/updateDate()
which would serve as a common function to update dates.
- The naming of my action creators, reducers, and functions inside
mapDispatchToProps
are usually the same. For exampleUPDATE_ACTIVE_INGREDIENTS
,updateActiveIngredients
action creator andupdateActiveIngredients
function. Is there a better approach to this?
This is fine, so long as you stick with it. You anyways don't have to worry about the ActionCreator
names because, if you access the methods/functions that are defined in mapDispatchToProps()
and don't directly dispatch()
actions in the Component
itself, you wouldn't find much problems with understanding what goes where.
What I mean is, if you use - this.props.updateActiveIngredients()
- then it is fine. However, if you use - this.props.dispatch(updateActiveIngredients())
you might get confused.
3.In the
addRecipe
function insidemapDispatchToProps
, I am validating thename
andingredients
values. Should this go inside this function, or somewhere else?
Technically, it is a function. You can move it to a named function, but for something this small this is alright.
4.In the
addRecipe
function insidemapDispatchToProps
, I am dispatching many actions. Is this correct practice?
This is correct. You can also add a redux-thunk
middleware and add an ActionCreator
that returns a function instead of a simple JSON object.
For e.g.:
const mapDispatchToProps = (dispatch, ownProps) => {
return {
addRecipe: (id, name, ingredients) => {
if (name && ingredients) {
dispatch(addRecipe(id, name, ingredients))
dispatch(updateActiveName(''))
dispatch(updateActiveIngredients(''))
saveStoreRecipesToLocalStorage(ownProps.store.getState())
}
}
}
}
could be replaced by something like this -
const mapDispatchToProps = (dispatch, ownProps) => {
return {
addRecipe: (id, name, ingredients) => dispatch(addRecipe(id, name, ingredients))
}
}
// Action Creator
addRecipe(id, name, ingredients) => {
return (dispatch, getState) => {
if (name && ingredients) {
dispatch(addRecipe(id, name, ingredients))
dispatch(updateActiveName(''))
dispatch(updateActiveIngredients(''))
// Get the proper state that you want using getState()
const something = getState()
saveStoreRecipesToLocalStorage(something)
}
}
}
add a comment |
The one thing that I would change is that since you are already using react-redux
library, you might as well do this -
let store = createStore(recipes, window.__REDUX_DEVTOOLS_EXTENSION__())
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
So that you don't have to manually subscribe
like so -
store.subscribe(render)
render()
About the questions that you mentioned -
- I have an
updateActiveName
andupdateActiveIngredients
function because there are only these two fields that get added by the form. Would it be better instead to use anupdateActiveObject
and run this when either field changes? I see this as a benefit for when other fields get added, but less efficient because I will be running anonChange
function for several fields that aren't changing.
This is again an opinion, but I wouldn't do that. One reason is that they don't/might not have similar types.
An example where this might work is - say - you have a big form where you have to enter a number of dates, one for birthdate, one for date of joining the company etc. You could a function changeDate()/updateDate()
which would serve as a common function to update dates.
- The naming of my action creators, reducers, and functions inside
mapDispatchToProps
are usually the same. For exampleUPDATE_ACTIVE_INGREDIENTS
,updateActiveIngredients
action creator andupdateActiveIngredients
function. Is there a better approach to this?
This is fine, so long as you stick with it. You anyways don't have to worry about the ActionCreator
names because, if you access the methods/functions that are defined in mapDispatchToProps()
and don't directly dispatch()
actions in the Component
itself, you wouldn't find much problems with understanding what goes where.
What I mean is, if you use - this.props.updateActiveIngredients()
- then it is fine. However, if you use - this.props.dispatch(updateActiveIngredients())
you might get confused.
3.In the
addRecipe
function insidemapDispatchToProps
, I am validating thename
andingredients
values. Should this go inside this function, or somewhere else?
Technically, it is a function. You can move it to a named function, but for something this small this is alright.
4.In the
addRecipe
function insidemapDispatchToProps
, I am dispatching many actions. Is this correct practice?
This is correct. You can also add a redux-thunk
middleware and add an ActionCreator
that returns a function instead of a simple JSON object.
For e.g.:
const mapDispatchToProps = (dispatch, ownProps) => {
return {
addRecipe: (id, name, ingredients) => {
if (name && ingredients) {
dispatch(addRecipe(id, name, ingredients))
dispatch(updateActiveName(''))
dispatch(updateActiveIngredients(''))
saveStoreRecipesToLocalStorage(ownProps.store.getState())
}
}
}
}
could be replaced by something like this -
const mapDispatchToProps = (dispatch, ownProps) => {
return {
addRecipe: (id, name, ingredients) => dispatch(addRecipe(id, name, ingredients))
}
}
// Action Creator
addRecipe(id, name, ingredients) => {
return (dispatch, getState) => {
if (name && ingredients) {
dispatch(addRecipe(id, name, ingredients))
dispatch(updateActiveName(''))
dispatch(updateActiveIngredients(''))
// Get the proper state that you want using getState()
const something = getState()
saveStoreRecipesToLocalStorage(something)
}
}
}
add a comment |
The one thing that I would change is that since you are already using react-redux
library, you might as well do this -
let store = createStore(recipes, window.__REDUX_DEVTOOLS_EXTENSION__())
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
So that you don't have to manually subscribe
like so -
store.subscribe(render)
render()
About the questions that you mentioned -
- I have an
updateActiveName
andupdateActiveIngredients
function because there are only these two fields that get added by the form. Would it be better instead to use anupdateActiveObject
and run this when either field changes? I see this as a benefit for when other fields get added, but less efficient because I will be running anonChange
function for several fields that aren't changing.
This is again an opinion, but I wouldn't do that. One reason is that they don't/might not have similar types.
An example where this might work is - say - you have a big form where you have to enter a number of dates, one for birthdate, one for date of joining the company etc. You could a function changeDate()/updateDate()
which would serve as a common function to update dates.
- The naming of my action creators, reducers, and functions inside
mapDispatchToProps
are usually the same. For exampleUPDATE_ACTIVE_INGREDIENTS
,updateActiveIngredients
action creator andupdateActiveIngredients
function. Is there a better approach to this?
This is fine, so long as you stick with it. You anyways don't have to worry about the ActionCreator
names because, if you access the methods/functions that are defined in mapDispatchToProps()
and don't directly dispatch()
actions in the Component
itself, you wouldn't find much problems with understanding what goes where.
What I mean is, if you use - this.props.updateActiveIngredients()
- then it is fine. However, if you use - this.props.dispatch(updateActiveIngredients())
you might get confused.
3.In the
addRecipe
function insidemapDispatchToProps
, I am validating thename
andingredients
values. Should this go inside this function, or somewhere else?
Technically, it is a function. You can move it to a named function, but for something this small this is alright.
4.In the
addRecipe
function insidemapDispatchToProps
, I am dispatching many actions. Is this correct practice?
This is correct. You can also add a redux-thunk
middleware and add an ActionCreator
that returns a function instead of a simple JSON object.
For e.g.:
const mapDispatchToProps = (dispatch, ownProps) => {
return {
addRecipe: (id, name, ingredients) => {
if (name && ingredients) {
dispatch(addRecipe(id, name, ingredients))
dispatch(updateActiveName(''))
dispatch(updateActiveIngredients(''))
saveStoreRecipesToLocalStorage(ownProps.store.getState())
}
}
}
}
could be replaced by something like this -
const mapDispatchToProps = (dispatch, ownProps) => {
return {
addRecipe: (id, name, ingredients) => dispatch(addRecipe(id, name, ingredients))
}
}
// Action Creator
addRecipe(id, name, ingredients) => {
return (dispatch, getState) => {
if (name && ingredients) {
dispatch(addRecipe(id, name, ingredients))
dispatch(updateActiveName(''))
dispatch(updateActiveIngredients(''))
// Get the proper state that you want using getState()
const something = getState()
saveStoreRecipesToLocalStorage(something)
}
}
}
The one thing that I would change is that since you are already using react-redux
library, you might as well do this -
let store = createStore(recipes, window.__REDUX_DEVTOOLS_EXTENSION__())
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
So that you don't have to manually subscribe
like so -
store.subscribe(render)
render()
About the questions that you mentioned -
- I have an
updateActiveName
andupdateActiveIngredients
function because there are only these two fields that get added by the form. Would it be better instead to use anupdateActiveObject
and run this when either field changes? I see this as a benefit for when other fields get added, but less efficient because I will be running anonChange
function for several fields that aren't changing.
This is again an opinion, but I wouldn't do that. One reason is that they don't/might not have similar types.
An example where this might work is - say - you have a big form where you have to enter a number of dates, one for birthdate, one for date of joining the company etc. You could a function changeDate()/updateDate()
which would serve as a common function to update dates.
- The naming of my action creators, reducers, and functions inside
mapDispatchToProps
are usually the same. For exampleUPDATE_ACTIVE_INGREDIENTS
,updateActiveIngredients
action creator andupdateActiveIngredients
function. Is there a better approach to this?
This is fine, so long as you stick with it. You anyways don't have to worry about the ActionCreator
names because, if you access the methods/functions that are defined in mapDispatchToProps()
and don't directly dispatch()
actions in the Component
itself, you wouldn't find much problems with understanding what goes where.
What I mean is, if you use - this.props.updateActiveIngredients()
- then it is fine. However, if you use - this.props.dispatch(updateActiveIngredients())
you might get confused.
3.In the
addRecipe
function insidemapDispatchToProps
, I am validating thename
andingredients
values. Should this go inside this function, or somewhere else?
Technically, it is a function. You can move it to a named function, but for something this small this is alright.
4.In the
addRecipe
function insidemapDispatchToProps
, I am dispatching many actions. Is this correct practice?
This is correct. You can also add a redux-thunk
middleware and add an ActionCreator
that returns a function instead of a simple JSON object.
For e.g.:
const mapDispatchToProps = (dispatch, ownProps) => {
return {
addRecipe: (id, name, ingredients) => {
if (name && ingredients) {
dispatch(addRecipe(id, name, ingredients))
dispatch(updateActiveName(''))
dispatch(updateActiveIngredients(''))
saveStoreRecipesToLocalStorage(ownProps.store.getState())
}
}
}
}
could be replaced by something like this -
const mapDispatchToProps = (dispatch, ownProps) => {
return {
addRecipe: (id, name, ingredients) => dispatch(addRecipe(id, name, ingredients))
}
}
// Action Creator
addRecipe(id, name, ingredients) => {
return (dispatch, getState) => {
if (name && ingredients) {
dispatch(addRecipe(id, name, ingredients))
dispatch(updateActiveName(''))
dispatch(updateActiveIngredients(''))
// Get the proper state that you want using getState()
const something = getState()
saveStoreRecipesToLocalStorage(something)
}
}
}
answered Mar 22 at 10:50
yadav_vi
351317
351317
add a comment |
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f187274%2fredux-recipe-list%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown