I'm having a problem with redux.
when I use
store.dispatch( addExpense( {description: "Rent"} ) );
in my app.js file it works and the object is added. But when I try to use it in a context of an component in a separated file it doesn't. The console does't throw any error.
When I use other action like "search" in the same component it works fine. So there's no problem with the connection. It seems that for some reasons it can't change the state.
//ACTIONS
export const addExpense = ( { description="", amount=0 } = {}) => ({
type: "ADD_EXPENSE",
expense: {
description,
amount
}
})
//REDUCERS
const expenseReducer = ( state = [], action) => {
switch(action.type) {
case "ADD_EXPENSE":
return [...state, action.expense]
case "EDIT_EXPENSE": //<- this works
return state.map( (expense) => {
if (expense.id === action.id)
return {
...expense, ...action.update }
else return expense
} )
default: return state
}
const filterReducer = ( state = {text:""}, action) => {
switch(action.type){
case "FIND_TEXT": //<- this works
return { ...state, text:action.text }
default: return state;
}
}
//COMPONENT
const AddEx = ( props ) => (
<div>
<form onSubmit={(e) => {
e.preventDefault;
props.dispatch(addExpense ( {description: document.getElementById("addedEx").value} ))
console.log(document.getElementById("addedEx").value);
//it shows the correct value in the console but the state stays unchanged
} } >
<input type="text" id="addedEx"/>
<button type="submit">submit</button>
</form>
//SEARACH -> works
<input
type="text" value={props.filter.text}
onChange={(e) => {
props.dispatch(findText({text:e.target.value}))
}}
/>
</div>
)
You are adding the value to state instead of expenses, do return [...state.expense, action.expense]
//REDUCERS
const expenseReducer = (state = [], action) => {
switch (action.type) {
case 'ADD_EXPENSE':
return {
...state,
expense: [...state.expense, action.expense]
};
case 'EDIT_EXPENSE': //<- this works
return state.map(expense => {
if (expense.id === action.id)
return {
...expense,
...action.update
};
else return expense;
});
default:
return state;
}
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.0.3/react-redux.min.js"></script>
<script src="http://wzrd.in/standalone/uuid%2Fv1#latest"></script>
<div id="root"></div>
<script type="text/babel">
const { Provider, connect } = ReactRedux;
const { applyMiddleware, createStore, combineReducers } = Redux;
const ADD_EXPENSE = 'ADD_EXPENSE';
function addExpense(payload) {
return { type: ADD_EXPENSE, payload };
}
const initialState = {
expense: [],
};
function rootReducer(state = initialState, action) {
if (action.type === ADD_EXPENSE) {
return {
...state,
expense: [...state.expense, action.payload]
};
}
return state;
}
const store = createStore(rootReducer);
const mapStateToProps = state => {
return { expense: state.expense };
};
function mapDispatchToProps(dispatch) {
return {
addExpense: expense => dispatch(addExpense(expense))
};
}
const ConnectedList = ({ expense, addExpense }) => {
return (
<div>
<ul className="list-group list-group-flush">
{expense.map(el => (
<li className="list-group-item" key={el.id}>
{`${el.description} - $${el.amount}`}
</li>
))}
</ul>
</div>
);
};
const List = connect(
mapStateToProps,
)(ConnectedList);
class ExpenseForm extends React.Component {
state = {
description: '',
amount: 0,
}
handleSubmit = (e) => {
e.preventDefault();
if (!this.state.description || this.state.amount === 0) {
return
}
const { description, amount } = this.state;
this.props.addExpense({
description,
amount,
id: uuidv1()
});
this.setState({
description: '',
amount: 0
})
}
handleInput = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input name="description" placeholder="Description" onChange={this.handleInput} value={this.state.description} />
<input type="number" name="amount" placeholder="Amount" onChange={this.handleInput} value={this.state.amount} />
<input type="submit" />
</form>
)
}
}
const Form = connect(null, mapDispatchToProps)(ExpenseForm);
class App extends React.Component {
render() {
return (
<div>
<List />
<Form />
</div>
);
}
}
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
</script>
I found the solution. It didn't work because e.preventDefault lacked the (). so simple change from e.preventDefault to e.preventDefault () fixed it. silly mistake.
Related
I have a component that renders a child component. The child component manages its own state, and I am trying to pass the updated state to the parent, using a callback, to no success. I have tried for hours to identify the problem but haven't been able to figure it out. My parent component is this (without imports):
const formReducer = (state, action) => {
switch (action.type) {
case 'SET_CATEGORY':
return {
category: action.category
};
default:
return state;
}
};
function LogService() {
const initialFormState =
{
category: ''
};
const [formState, dispatch] = useReducer(formReducer, initialFormState);
const getCategoryState = useCallback(category => {
dispatch({
action: 'SET_CATEGORY',
category: category
}, []);
}
);
const submitHandler = event => {
event.preventDefault();
console.log(formState);
};
return (
<>
<form onSubmit={submitHandler}>
<CategoryOne sendState={getCategoryState} />
<button>send</button>
</form>
</>
);
};
export default LogService;
And this is my child component:
const selectionReducer = (state, action) => {
switch (action.type) {
case 'DISPLAY_SELECTION':
return {
...state,
selectionIsVisible: true
};
case 'LOG_SELECTION':
return {
...state,
category: action.category,
selectionIsVisible: false
}
default:
return state;
}
};
function CategoryOne(props) {
const [selectionState, dispatch] = useReducer(selectionReducer, {});
const { category } = selectionState;
const { sendState } = props;
useEffect(() => {
sendState(category);
}, [category, sendState])
const displaySelectionHandler = event => {
dispatch({
type: 'DISPLAY_SELECTION'
});
};
const selectCategoryHandler = event => {
dispatch({
type: 'LOG_SELECTION',
category: event.target.id
});
};
return (
<>
<div className="container">
<div className={`options-container ${selectionState.selectionIsVisible && 'active'}`}>
<div className="option">
<input className="radio" type="radio" id="One" name="category" onChange={selectCategoryHandler} />
</div>
<div className="option">
<input className="radio" type="radio" id="Two" name="category" onChange={selectCategoryHandler} />
</div>
<div className="option">
<input className="radio" type="radio" id="Three" name="category" onChange={selectCategoryHandler} />
</div>
<div className="option">
<input className="radio" type="radio" id="Four" name="category" onChange={selectCategoryHandler} />
</div>
</div>
<div className="selected" id="category" onClick={displaySelectionHandler}>
Category
</div>
</div>
</>
);
};
export default CategoryOne;
I am not getting Errors, just a Warning:
React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?
I don't understand that warning, I do have an array of dependencies. other than that, selectionIsVisible: true (in the 'DISPLAY_SELECTION' action) works, but selectionIsVisible: false (in the 'LOG_SELECTION' action).
Could someone please help me out? I am very frustrated.
Because use put [] wrong place. Just update like this:
const getCategoryState = useCallback((category) => {
dispatch({
action: "SET_CATEGORY",
category: category,
});
}, []);
I have problem with calling componentDidMount.
Situation:
I have 2 reducers. They called in different components and do not intersect. In every component in ComponentDidMount i calling function from Reducer which set data. In different ways one of two is not working and function from reducer is not called. I have error like "Cannot read property of null". After reload page working of components is changed.
First Reducer:
const SET_VIDEOS = 'SET_VIDEOS';
const TOOGLE_IS_FETCHING = 'TOOGLE_IS_FETCHING';
let initialState = {
videos: null,
isFetching: null
}
const youtubeReducer = (state = initialState, action) => {
switch(action.type){
case SET_VIDEOS: {
console.log("muerto.");
return {...state, videos: action.videos}
}
case TOOGLE_IS_FETCHING: {
return {...state, isFetching: action.isFetching}
}
default:{
return state;
}
}
}
export const setVideos = (videos) => ({
type: SET_VIDEOS, videos
});
export const setIsFetching = (isFetching) => ({
type: TOOGLE_IS_FETCHING, isFetching
});
export const getVideosThunkCreator = () => {
return(dispatch) => {
dispatch(setIsFetching(true));
console.log("ALO");
youtubeApi.getVideos().then(data => {
dispatch(setVideos(data.items));
dispatch(setIsFetching(false));
console.log(data.items);
})
}
}
Second Reducer:
let initialState = {
dj: null,
isFetching: null
}
const djReducer = (state = initialState, action) => {
switch(action.type){
case SET_DJ:{
console.log("muerto2.");
return {...state, dj: action.dj}
}
case TOOGLE_IS_FETCHING: {
return {...state, isFetching: action.isFetching}
}
default:
return state;
}
}
export const setDj = (dj) => ({
type: SET_DJ, dj
});
export const setIsFetching = (isFetching) => ({
type: TOOGLE_IS_FETCHING, isFetching
});
Container Component with First Reducer:
class LivesContainer extends React.Component{
componentDidMount(){
this.props.getVideosThunkCreator();
console.log(this.props);
}
render(){
return(
<>
{this.props.isFetching ? <Preloader/> : <Lives videos={this.props.videos}/>}
</>
);
}
}
let mapStateToProps = (state) => {
return{
videos: state.youtubeReducer.videos,
isFetching: state.youtubeReducer.isFetching
}
}
export default connect(mapStateToProps, {
getVideosThunkCreator,
setIsFetching
})(LivesContainer);
Container Component with Second Reducer:
class DjContainer extends React.Component {
componentDidMount(){
console.log("MOUNT");
let djId = this.props.match.params.djId;
if(!djId){
djId = 0;
}
let djs = this.props.djs;
this.props.setIsFetching(true);
console.log(djs);
console.log(djId);
djs.forEach(dj => {
if(dj.id == djId){
this.props.setDj(dj);
this.props.setIsFetching(false);
}
});
console.log(this.props);
}
componentWillUnmount(){
console.log("UNMAUNT");
}
render(){
return(
<>
{this.props.isFetching ? <Preloader/>:
<Dj {...this.props} dj={this.props.dj} />
}
</>
);
}
}
let mapStateToProps = (state) => ({
dj: state.djReducer.dj,
djs: state.djsReducer.djs,
isFetching: state.djReducer.isFetching
});
let WithUrlDataContainerComponent = withRouter(DjContainer);
export default connect(mapStateToProps,{
setDj,
setIsFetching
})(WithUrlDataContainerComponent);
First Component where i have errors:
const Lives = (props) => {
let bannerId;
let videoBanner = "https://www.youtube.com/embed/";
let videos = [];
props.videos.forEach((video,index) => {
if(index == 0){
bannerId = video.contentDetails.videoId;
}else{
videos.push(video);
}
});
console.log(videos);
videoBanner += bannerId;
let videosMap = videos.map((video,index) => {
return <Video video={video} key={index}/>
})
return(
<div className={classes.main}>
<div className={classes.container}>
<h1>Our videos</h1>
<div className={classes.videos}>
<iframe width="90%" height="80%" src={videoBanner} frameborder="0" allow="accelerometer; autoplay=1; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<div className={classes.videoList}>
{videosMap}
</div>
</div>
</div>
</div>
);
}
error from there:
props.videos.forEach((video,index) => {
if(index == 0){
bannerId = video.contentDetails.videoId;
}else{
videos.push(video);
}
});
From Secornd Component:
const Dj = (props) => {
console.log(props);
let video = "https://www.youtube.com/embed/" + props.dj.video;
return(
<div className={classes.main}>
<div className={classes.container}>
<h1>{props.dj.name}</h1>
<iframe width="90%" height="80%" src={video} frameborder="0" allow="accelerometer; autoplay=1; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<div className={classes.links}>
<a href={props.dj.fb} target="_blank">
<img src={fb}/>
</a>
<a href={props.dj.sound} target="_blank">
<img src={sound}/>
</a>
<a href={props.dj.inst} target="_blank">
<img src={inst}/>
</a>
</div>
</div>
</div>
);
}
Error from there:
let video = "https://www.youtube.com/embed/" + props.dj.video;
I am stuck around a project and honestly I don't know how to solve it (I am quite new before you judge)
so this is my code:
class EditProfile extends Component {
state = {
företagsnamn: '',
organisationsnummer: '',
};
handleChange = e => {
this.setState({
[e.target.id]: e.target.value
});
};
handleSubmit = e => {
e.preventDefault();
// console.log(this.state);
this.props.editProfile(this.state);
this.props.history.push("/dash");
};
render() {
const { auth, profile } = this.props;
if (auth.isEmpty) return <Redirect to="/dash" />;
return (
<div >
<form className="white" onSubmit={this.handleSubmit}>
<div className="row">
<div className="col xl6 l6 m6 s12">
<label>Foretagsnamn:</label>
<input
type="text"
disabled
placeholder={profile.foretagsnamn}
id="foretagsnamn"
onChange={this.handleChange}
/>
</div>
<div className="col xl6 l6 m6 s12">
<label>organisationsnummer:</label>
<input
type="number"
placeholder={profile.organisationsnummer}
id="organisationsnummer"
onChange={this.onChange}
/>
</div>
</div>
<div className="input-field">
<button className="btn orange lighten-1" style={{width:'100%'}} >Submit</button>
{ }
</div>
</form>
</div>
}}
const mapStateToProps = state => {
return {
auth: state.firebase.auth,
profile: state.firebase.profile
};
};
const mapDispatchToProps = dispatch => {
return {
editProfile: profil => dispatch(editProfile(profil))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(EditProfile);
this was the action
export const editProfile = (profil) => {
return (dispatch, getState, { getFirestore, getFirebase }) => {
const firebase = getFirebase();
const firestore = getFirestore();
const profile = getState().firebase.auth
console.log(profile)
const authorId = getState().firebase.auth.uid;
// const foretagsnamn = getFirestore().firestore.collection('users').doc(profile.uid).foretagsnamn
// firebase.auth()
firestore.collection('users').doc(profile.uid).set({
// foretagsnamn: foretagsnamn,
// organisationsnummer: profil.organisationsnummer,
adress: profil.adress,
ort: profil.ort,
telefonnummer: profil.telefonnummer,
postnummer: profil.postnummer,
}, { merge: true }
).then(() => {
dispatch({ type: 'UPDATE_SUCCESS' });
}).catch(err => {
dispatch({ type: 'UPDATE_ERROR' }, err);
});
}}
and this the reducer
const editProfileReducer = (state = initState, action) => {
switch (action.type) {
case "EDITPROFILE_ERROR":
return {
...state,
editError: action.error
};
case "EDITPROFILE_SUCCESS":
return {
...state,
user:action.user
};
default:
return state;
}
}
export default editProfileReducer;
however when I press the button submit it shows this error:
FirebaseError: Function CollectionReference.doc() requires its first argument to be of type non-empty string, but it was: undefined
PS: Solved. The action was wrong. I changed ´´´const profile = getState().firebase.auth.```**instead of profile. **
Stays open if someone needs.
I am trying to fetch github users on providing username, but unable to
fetch. Can anyone tell what I am missing , as I see the code seem
perfect.
I am trying to fetch github users on providing username, but unable to
fetch. Can anyone tell what I am missing , as I see the code seem
perfect.
Below is my code for component, action, reducer
class Navbar extends Component {
constructor(props) {
super(props);
this.state = {
userName: ""
};
this.searchUser = this.searchUser.bind(this);
this.onChange = this.onChange.bind(this);
}
componentDidMount(){
console.log(this.props.users)
}
searchUser(e) {
e.preventDefault();
this.props.searchUser(this.state.userName);
this.setState({ userName: "" });
}
onChange(e) {
this.setState({ [e.target.id]: e.target.value }, () =>
console.log(this.state.userName)
);
}
render() {
return (
<nav className="navbar fixed-top navbar-expand-md navbar-light bg-primary">
<div className="container">
<h4>Github Search</h4>
<form className="form-inline my-1 my-lg-0" onSubmit={this.searchUser}>
<div className="input-group add-on">
<input
type="search"
className="form-control mr-sm-2 border-right-0 border"
id="userName"
placeholder="Search User..."
aria-label="Search"
onChange={this.onChange}
value={this.state.userName}
/>
</div>
</form>
</div>
</nav>
);
}
}
const mapStateToProps = state => {
return {
users: state.users
};
};
export default connect(
mapStateToProps,
{ searchUser }
)(Navbar);
-------------------------------------
export const searchUser = userName => dispatch => {
console.log("----" + userName);
fetch("https://api.github.com/search/users?q=" + userName)
.then(res => res.json())
.then(users =>
dispatch({
type: "FETCH_USERS",
payload: users.items
})
);
};
--------------------------------------------
export default function(state = { users: [] }, action) {
switch (action.type) {
case "FETCH_USERS":
return {
...state,
users: action.payload
};
default:
return state;
}
}
--------------------------------------------
I am able to pass submitted username to actions but after dispatch
there seem some error.
How to check if each state has value then combine all values?
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
inputvalue : '',
allval: ''
}
}
onChangeOfInput =(name,value) =>{
this.setState({
[name]: value
});
}
getValues = () =>{
console.log(this.state);
if(this.state.Title1) {
this.setState({
allval: this.state.allval+this.state.Title1
});
}
}
render() {
return (
<div className="hello">
<Input onChangeOfInput={this.onChangeOfInput}
placeholder="Title 1" name="Title1" />
<br/>
<Input placeholder="Title 2" name="Title2" onChangeOfInput={this.onChangeOfInput} />
<br/>
<Input placeholder="Title 3" name="Title3" onChangeOfInput={this.onChangeOfInput}/>
<br/>
<Input placeholder="Title 4" name="Title4" onChangeOfInput={this.onChangeOfInput}/>
<br/>
<button onClick={this.getValues}>Get value</button>
</div>
)
}
}
class Input extends React.Component {
constructor(props) {
super(props)
this.state = {
inputvalue: ''
}
}
handleChange(e) {
this.setState({
inputvalue: e.target.value
});
this.props.onChangeOfInput(this.props.name,e.target.value)
}
render() {
return (
<input
type="text"
placeholder={this.props.placeholder}
value={this.state.inputvalue}
onChange={this.handleChange.bind(this)}
/>
)
}
}
ReactDOM.render(<App />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
jsfiddle: https://jsfiddle.net/vxm2ojLz/
The issue is here, I need to check each value state.Title1, state.Title2, state.Title3, state.Title4 if they are not empty, then I want to combine all values if it is not empty and assign the combined values to allVal, how to combine all values to allval? Thanks
You need to be doing something like this.
getValues = () => {
console.log(this.state);
let combinedString = "";
Object.keys(this.state)
.map( igKey => {
if(this.state[igKey] != "" && igKey.includes('Title')){
combinedString = combinedString +''+ this.state[igKey];
return combinedString
}
});
this.setState({allval:combinedString})
console.log(combinedString);
}
working fiddle https://jsfiddle.net/2nhc6drm/
hope this helps!
Try handling getValues like this:
getValues = () =>{
console.log(this.state);
let result = [];
Object.keys(this.state).forEach(key => {
if (key.includes('Title') && this.state[key]) result.push(`${key}: ${this.state[key]}`);
})
this.setState({
allval: result.join('; ')
})
}
Please Update getValues method :-
For concatination,it will ignore the keys allval and inputval.
getValues = () => {
let allval = ''
for(let key of Object.keys(this.state)){
if(key==='allval' || key==='inputval'){
continue;
}
else{
let value=this.state[key];
console.log(value);
if(value===''){
}
else{
allval=allval+value;
}
console.log(allval);
}
}
this.setState({allval:allval})
}
Working SandBox :- https://codesandbox.io/s/vqoxo9w1wy
Hope this helps,
Cheers !!
I'd recommend to use reduce for combinde the values, and use the functional setState to avoid double state change:
class App extends React.Component {
state = {
allVal: '',
title1: '',
title2: ''
}
getValues = (prevState, name, newVal) => {
return Object.keys(prevState)
.reduce((acc, key) => {
if (key === 'allVal') return acc;
if (key === name) return acc + newVal;
return acc + prevState[key];
}, '')
}
handleChange = ({ target: { name, value } }) => {
this.setState(prevState => ({
[name]: value,
allVal: this.getValues(prevState, name, value)
}))
}
render(){
const { title1, title2, allVal } = this.state;
return (
<div>
<input name="title1" onChange={this.handleChange} value={title1} /><br />
<input name="title2" onChange={this.handleChange} value={title2} /><br />
allVal: <span>{allVal}</span>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>