siamak alipour
siamak alipour

Reputation: 63

React redux state is undefined on first render despite of initlizedState

Redux state is undefined in first render and I returned the initilizedState = {} in my reducer

store.js

const store = createStore(
    rootReducer,

    compose(
        applyMiddleware(thunk),
        window.devToolsExtension ? window.devToolsExtension() : (f) => f
    )
)

export default store

rootReducer.js

const rootReducer = combineReducers({
    search: searchReducer,
    product: productReducer,
})

export default rootReducer

reducer.js

const initialState = {}
const productReducer = (state = initialState, action) => {
    const { type, payload } = action
    switch (type) {
        case PRODUCTS_ALL:
            console.log('reducer')
            return { ...state, items: payload }
        default:
            return state
    }
}

export default productReducer

action.js

const products = axios.create({
    baseURL: 'http://localhost:8001/api/products',
})
export const allProducts = () => async (dispatch) => {
    console.log('fetching')
    await products.get('/').then((res) => {
        dispatch({
            type: PRODUCTS_ALL,
            payload: res.data,
        })
    })
}

And although I used connect() in my feed container

Feed.js

function Feed({ allProducts, product }) {
    const [productItems, setProductItems] = useState()
    const [loading, setLoading] = useState(false)

    React.useEffect(() => {
        allProducts()
        console.log(product)
    }, [])

    return (
        
                            
            <div className='feed__content'>
                {loading ? (
                    <Loader
                        type='Oval'
                        color='#212121'
                        height={100}
                        width={100}
                    />
                ) : (
                    <div className='feed__products'>
                        <div className='feed__productsList'>
                             {product.map((product) => {
                                return (
                                    <Product
                                        name={product.name}
                                        image={product.image}
                                        price={product.price}
                                    />
                                )
                            })} 
                        </div>
                        
                    </div>
                )}

        </div>
    )
}

const mapStateToProps = (state) => ({
    product: state.product.items,
})
const mapDispatchToProps = (dispatch) => {
    return {
        allProducts: () => dispatch(allProducts()),
    }
}
export default connect(mapStateToProps, mapDispatchToProps)(Feed)

if you look at the data on the console you will see undefined but if I add a useEffect dependency it will create a loop , and the first of them is undefined and the rest are the data that I want. because of this problem when I want to render products with map , it throws an error that said can't map undefiend .

How can I solve this problem

Upvotes: 2

Views: 8480

Answers (4)

Sabaoon Bedar
Sabaoon Bedar

Reputation: 3689

Problem statement: I would be giving you three really best methods, in order to solve it, the problem appears when redux is transferring states to the components, so it seems that the render is faster than the response of props to the component.

So, when you have props undefined by their value your application crashes.


note: I will be taking a general example and will show you how to avoid this error.

For example, I have a component in which I want to render the data from the redux response (with mapPropsToState), what I need to do is only to check it for undefined and then if it is undefined we need to use the conditional (ternary) operator for if and else statements.

//suppose you will get an address object from api ,which will contain city ,area ,street ,pin code etc
// address={} intially address is a blank object
render(){
    return(
        (typeof address.area!='undefined')?
        <div>
        <div>{address.city}</div>
        <div>{address.street}</div>
        <div>{address.pin_code}</div>
        <div>{address.area}</div>
        </div>
        :<div>loading....</div>
    )
}

And another way is to simply use the package of loadash in loadash you can achieve the same by calling the function "isUndefined" you can also use it in the inputFields likewise

<FloatingLabel  label="Username">
                  <Form.Control
                    type="input"
                    placeholder=" "
                    name="username"
                    value={props.username}
                    defaultValue={_.isUndefined(props.userEditResponse)?'':props.userEditResponse.username}

                    required
                  onChange={(e)=>onInputchange(e)}
                 

                  />
                </FloatingLabel>
   //note: you can also use (typeof props.userEditResponse!='undefined')?'do somthing':'leave it'

loadash installation:

npm i lodash then in your component import the the below line and then you can use it, the way i used in the above example.

import _ from 'lodash'

note: the answer was just to give you an idea and this was really a panic problem that is why I shared it with you guys, it might help many of you, you can use a similar way in different situations. thanks!

furtherMore your can work with ?.:

The ?. operator is like the . chaining operator, except that instead of causing an error if a reference is nullish (null or undefined), the expression short-circuits with a return value of undefined. When used with function calls, it returns undefined if the given function does not exist.

This results in shorter and simpler expressions when accessing chained properties when the possibility exists that a reference may be missing. It can also be helpful while exploring the content of an object when there's no known guarantee as to which properties are required.

      { props.rolesListSuccessResponse?.permissions.map((list,index) => (
 
    <div  className="mb-3 col-md-3" key={index}>
      <Form.Check 
        type={'checkbox'}
        id={list.name}
        label={list.name.charAt(0).toUpperCase()+list.name.slice(1)}
      />

    </div>
   
  ))}

Upvotes: 0

siamak alipour
siamak alipour

Reputation: 63

Joel Jaimon code worked well .

and in my code I added

const initialState = {
  items: []
}

according to @Andrew and product?.map so my code works well now.

Upvotes: 2

Joel Jaimon
Joel Jaimon

Reputation: 302

A/c to your code you're trying to make an async action in redux. You should use redux-saga or redux-thunk for this.

But you can achieve your output in the following way, without using redux-saga or thunk.

Modify your code in the following way:

Feed.js

function Feed({ allProducts, product }) {
  // Set loading to true inititally
  const [loading, setLoading] = useState(true);

  React.useEffect(() => {
    // Make your api call here once its complete and you get a res, 
    // dispatch the referred action with response as payload.
    (async () => {
        const products = axios.create({
            baseURL: "http://localhost:8001/api/products",
          });
        const {data} = await products.get("/");
        allProducts(data);
    })()
  // This call is only made when you load it for the first time, if it 
  // depends 
  // on something add that dependencies in the dependency array of // 
  // useEffect.
  }, []);

  // once store is updated with the passed payload. Set loading to 
  // false.
  React.useEffect(() => {
    if(product){
        setLoading(false);
    }
  }, [product])

  return (
    <div className="feed__content">
      {loading ? (
        <Loader type="Oval" color="#212121" height={100} width={100} />
      ) : (
        <div className="feed__products">
          <div className="feed__productsList">
            {product.map((product) => {
              return (
                <Product
                  name={product.name}
                  image={product.image}
                  price={product.price}
                />
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}


const mapStateToProps = ({product}) => ({
  product: product?.items,
});

// here we have a payload to pass
const mapDispatchToProps = (dispatch) => {
  return {
    allProducts: (payload) => dispatch(allProducts(payload)),
  };
};
export default connect(mapStateToProps, mapDispatchToProps)(Feed);

Action

export const allProducts = (payload) => ({   
      type: "PRODUCTS_ALL",
      payload,
});

Reducer

const productReducer = (state = initialState, action) => {
    const { type, payload } = action
    switch (type) {
        case "PRODUCTS_ALL":
            return { ...state, items: payload }
        default:
            return state
    }
}

export default productReducer

Upvotes: 0

Andrew
Andrew

Reputation: 7545

In Feed.js, you aren't getting the entire slice of state. You are trying to access the key item inside.

const mapStateToProps = (state) => ({
    product: state.product.items,
})

Change your initialState to include that key and your code should be fine

const initialState = {
  items: []
}

Upvotes: 0

Related Questions