I have a component that displays a single Article object that is fetched from a Mongo database. It goes through my reducer and then I use mapStateToProps to put the state into this.props. I then want to display the data from that object but I cannot figure out how to do so. The page is called ArticleShow (It shows one article.)
ArticleShow.js
class ArticleShow extends Component {
componentDidMount() {
this.props.getArticle(this.props.id);
}
render() {
// I know this is wrong
const articleData = this.props.article;
return (
<Container>
</Container>
);
}
}
ArticleShow.propTypes = {
getArticle: PropTypes.func.isRequired,
article: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
article: state.article.articles,
});
export default connect(mapStateToProps, { getArticle })(ArticleShow);
articleReducer.js
const intialState = {
articles: [],
loading: false,
};
export default function (state = intialState, action) {
switch (action.type) {
case GET_ARTICLE:
return {
...state,
articles: {
[action.payload._id]: action.payload,
},
loading: false,
};
}
Below is the data when I console.log(this.props.article). It contains all the data I want to display but nothing that I do allows me to display the data.
this.props.article
5f0f80157f7feb0a2512130a:
author: "Shempton McGruntie"
body: "orem Ipsum is simply dummy text of the
printing and typesetting industry. Lorem Ipsum has
been "
date: "2020-07-15T22:15:49.787Z"
name: "Article_2"
__v: 0
_id: "5f0f80157f7feb0a2512130a"
__proto__: Object
-- Note: I can change the payload by changing the index from ._id to name, but I have the exact same problem and cannot access any of the data.
articleReducer.js
const intialState = {
articles: [],
loading: false,
};
export default function (state = intialState, action) {
switch (action.type) {
case GET_ARTICLE:
return {
...state,
articles: {
[action.payload.name]: action.payload,
},
loading: false,
};
}
this.props.article
Article_2:
author: "Shempton McGruntie"
body: "orem Ipsum is simply dummy text of the
printing and typesetting industry. Lorem Ipsum has
been "
date: "2020-07-15T22:15:49.787Z"
name: "Article_2"
__v: 0
_id: "5f0f80157f7feb0a2512130a"
__proto__: Object
remember, passing javascript inside jsx requires {}
change :
return (
<Container>
<p>articleData<p>
</Container>
);
to
return (
<Container>
<p>{articleData}<p>
</Container>
);
there is a tiny mistake I think related to articles in the initial state of the article reducer the response is object but the initial state of articles is array here is what I mean:
const intialState = {
articles: [],
loading: false,
};
export default function (state = intialState, action) {
switch (action.type) {
case GET_ARTICLE:
return {
...state,
articles: {
[action.payload.name]: action.payload,
},
loading: false,
};
}
so instead of array change it to object like this:
const intialState = {
articles: {},
loading: false,
};
export default function (state = intialState, action) {
switch (action.type) {
case GET_ARTICLE:
return {
...state,
articles: {
[action.payload.name]: action.payload,
},
loading: false,
};
}
This is how I used to debug if something is not accessible from my store state
const mapStateToProps = (state) => {
console.log("My store state ===>", state); // log the `state` and see the exact path for articles
return ({
article: state.article.articles,
});
}
Edit
Default case is missing in reducer. Hope you have missed posting full code.
export default function (state = intialState, action) {
switch (action.type) {
case GET_ARTICLE:
return {
...state,
articles: {
[action.payload._id]: action.payload,
},
loading: false,
};
default: return state; // You should have default case
}
}
Related
I am trying to create a webpage using React.
used redux for data access.
My problem is that when I click on any product, it shows me a perfect page, but if I go back and then again click on another product, my page shows me the data of the new product, but it does not change the photo height in CSS.
home page
product 1product 2(its doesnt changing first image height)
export const productDetailsReducer = (state = { product: {} }, action) => {
switch (action.type) {
case PRODUCT_DETAILS_REQUEST:
return {
loading: true,
...state <------i am sending the previous state
}
case PRODUCT_DETAILS_SUCCESS:
return {
loading: false,
product: action.payload,
}
case PRODUCT_DETAILS_FAIL:
return {
loading: false,
error: action.payload
}
case CLEAR_ERRORS:
return {
...state,
error: null
}
default:
return state
}
You must first iterate, and then provide the changes.
return {
...state <------ iterate
loading: true, <----- set new values
}
In addition, you should always iterate, just to make sure, you're not changing the whole state.
export const productDetailsReducer = (state = { product: {} }, action) => {
switch (action.type) {
case PRODUCT_DETAILS_REQUEST:
return {
...state
loading: true,
}
case PRODUCT_DETAILS_SUCCESS:
return {
...state,
loading: false,
product: action.payload,
}
case PRODUCT_DETAILS_FAIL:
return {
...state,
loading: false,
error: action.payload
}
case CLEAR_ERRORS:
return {
...state,
error: null
}
default:
return state
}
I have the following Reducer:
const initialState = {}
const dishReducer = (state = initialState, action) => {
switch (action.type) {
case 'LOAD_DISHES':
return (action.dishes)
case 'LOAD_DISHES_ERROR':
console.log("load dishes error")
return state
case 'LOAD_DISHES_SUCCESS':
console.log("load dishes success")
return state
default:
return state;
}
};
export default dishReducer;
And the following action(s):
import {database} from '../../config/fbConfig'
export const startLoadingDishes = (dishes) => {
return (dispatch) =>{
return database.ref('products-dishes').once('value').then((snapshot) => {
let dishes = {}
snapshot.forEach((childSnapshot) => {
let parentkey = childSnapshot.key
let dishArray = [];
childSnapshot.forEach((dish) =>{
dishArray.push(dish.val())
});
dishes[childSnapshot.key] = dishArray;
})
dispatch(loadDishes(dishes))
}).then(() => {
dispatch({ type: 'LOAD_DISHES_SUCCESS' });
}).catch(err => {
dispatch({ type: 'LOAD_DISHES_ERROR' }, err);
});
}
}
export const loadDishes = (dishes) => {
return {
type: 'LOAD_DISHES',
dishes
}
}
The 'startLoadingDishes' action is called inside the componentDidLoad() of a certain Component. However, I want to alter the initial state of my dishReducer so that it includes additional information, as follows:
const initialState = {
value : {},
loaded: false,
loading: false,
error: false
}
So now 'action.dishes' returned by reducer [in 'LOAD_DISHES' case] should be put inside the 'value' part of the state, instead of it being the whole state. Also, the 'loaded' part of the state should be set to true if dishes have already been loaded earlier, and so on. I understand this is fairly simple but as I am new to React+Redux, I don't know how to alter the Action/Reducer codes properly (while keeping state immutability). Any help is appreciated.
I originally asked the question, here is how I solved it (not sure if this is the 'best' way though):
New reducer file:
const initialState = {
value : {},
loaded: false,
loading: false,
error: false
}
const dishReducer = (state = initialState, action) => {
switch (action.type) {
case 'LOAD_DISHES':
return {
value: action.dishes,
loading: !state.loading,
loaded: false, //this might need to be set to true
error: false
}
case 'LOAD_DISHES_ERROR':
console.log("load dishes error")
return {
...state, //or: state.value, as the other parts of state are being overwritten below
loaded: false,
loading: false,
error: true
}
case 'LOAD_DISHES_SUCCESS':
console.log("load dishes success")
return {
...state, //better: state.value
loaded: true,
loading: false,
error: false
}
default:
return state;
}
};
export default dishReducer;
No change in actions file.
Now, inside the 'Main' component, I was originally accessing the state as such:
class Main extends Component {
componentDidMount() {
this.props.startLoadingDishes();
}
render() {
return (
//some code
)
}
}
const mapStateToProps = (state) => {
return {
dishes: state.dishes //to access dishes: dishes.value
}
}
export default connect(mapStateToProps, actions)(Main)
The Main component code also stayed the same, with the difference that now I use 'dishes.value' instead of just 'dishes' to access the value of dishes from the state (and dishes.loaded for loaded, and so on). And now the action caller inside componentDidMount is as follows:
componentDidMount() {
if(!this.props.dishes.loaded){
this.props.startLoadingDishes();
console.log("loading dishes from database")
}
}
I'm going to add an object to the array, the second time I want to add another object the whole array becomes number one and I end up with an error, my goal is to add a task to program Todo with Redux.
I also get this errors:
TypeError: Cannot read property 'length' of undefined
TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
//todoReducer.js
import {ADD_TODO} from '../Actions/Todo';
const initialState = {
todos:[],
};
const handleAddTodo = (state, action) => {
const {todos} = state;
const newTodo =[...todos, {
id: todos.length + 1,
text: action.title,
isComplete: false,
}]
return (
todos.push(newTodo)
)
}
export default function todoRDS(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return handleAddTodo(state, action)
default:
return state
}
}
Change your return function you return wrong value. You need to return the state
const handleAddTodo = (state, action) => {
const {todos} = state;
return {
...state,
todos: [...todos, {
id: todos.length + 1,
text: action.title,
isComplete: false,
}]
}
}
export default function todoRDS(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return {...state, todos: [...state.todos, { id: state.todos.length +1, title: action.title, isComplete: false }] }
default:
return state
}
}
state is unmutable in react and redux you need to create a new state with old state values and add your new todo inside that new object. If you still want to use handeAddTodo try this:
const handleAddTodo = (state, action) => {
return {...state, todos: [...state.todos, { id: state.todos.length +1, title: action.title, isComplete: false }] }
}
In logger seen anything is normal data loaded successfully.
You can see it by follow this link https://i.stack.imgur.com/uTLpr.jpg
But why this crypto props is undefined and how to fix it.
Does anyone have any idea?
Component
class CryptoContainer extends Component {
componentWillMount(){
this.props.Fetchcoin()
}
renderCoinCards() {
const { crypto } = this.props;
console.log('Crypto : '+crypto)
return crypto.data.map((coin,index) => //Add this
<CoinCard
key={coin.name}
coin_name={coin.name}
symbol={coin.symbol}
price_usd={coin.price_usd}
percent_change_24h={coin.percent_change_24h}
percent_change_7d={coin.percent_change_7d}
/>
)
}
}
function mapStatetoProps(state){
return{
crypto:state.crypto
}
}
export default connect(mapStatetoProps,{Fetchcoin})(CryptoContainer)
>
Action
export default function FetchCoin(){
return dispatch => {
dispatch({type:FETCHING_COIN_DATA})
return axios.get(`https://api.coinmarketcap.com//v1/ticker/?limit=10`)
.then(res => {
dispatch({type:FETCHING_COIN_DATA_SUCCESS,payload:res.data})
})
.catch(err => {
dispatch({type:FETCHING_COIN_DATA_FAIL,payload:err.data})
})
}
}
Reducer
const initialState = {
isFetching: null,
data: [],
hasError: false,
errorMessage: null
}
export default function (state = initialState, action) {
switch (action.type) {
case FETCHING_COIN_DATA:
return Object.assign({}, state, {
isFetching: true,
data: null,
hasError: false,
errorMessage: null
})
case FETCHING_COIN_DATA_SUCCESS:
return Object.assign({}, state, {
isFetching: false,
data: action.payload,
hasError: false,
errorMessage: null
})
case FETCHING_COIN_DATA_FAIL:
return Object.assign({}, state, {
isFetching: false,
data: action.payload,
hasError: true,
errorMessage: action.err
})
default:
return state
}
}
And store
const middleware = applyMiddleware(thunk,promise,logger)
const rootReducer = combineReducers({
crypto:CyptoReducer
})
const Store = createStore(
rootReducer,middleware
)
export default Store
I fixed misspelling in store but when I add crypto.data.map() in renderCoinCard
data seem still not working debugger shown
TypeError: Cannot read property 'map' of null
I think there is a problem with your declaration of rootReducer.Here you have mentioned "cypto" instead of "crypto". Fix this and it will work as here only redux declares a reducer and as you have declared here as "cypto" it expects "cypto" and fails.
I want to show a loading image when the user hits that particular URL and hide when API is successfully fetched the data.
I am not getting in react to show image initially where I should add code and how to hide when API response in success.
This is the action creator for fetching data.
export const fetchEvents = (requestData) => ({
type: FETCH_EVENTS,
payload: axios.get(requestData.url, fetchEventsConfig)
})
And this is the reducer for fetching data.
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_EVENTS_FULFILLED:
const oldState = Object.assign({}, state)
const response = Object.assign({}, action.payload.data)
const allResults = oldState.results.concat(response.results)
return {
...state,
...response,
results: allResults
}
}
}
I am new to this react redux so any help would be great
You can add a boolean property in your Redux state to indicate loading state. Let's call it isLoading. Set isLoading true when you dispatch FETCH_EVENTS. Set it false, when you dispatch FETCH_EVENTS_FULFILLED.
Pass value of isLoading to your React component using #connect HOF. Render a loading indicator when value is true, and content when it's false.
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_EVENTS: {
return {...state, isLoading: true}
}
case FETCH_EVENTS_FULFILLED: {
const response = action.payload.data
const results = [...state.results, ...response.results]
return {
...state,
...response,
results,
isLoading: false
}
}
case FETCH_EVENTS_FAILURE: {
return {...state, isLoading: false}
}
}
}
P.S.
Containing code inside braces after each switch/case limits scope of your const inside that block.
Write a loader file (loader.js)
import React from 'react'
import {ClipLoader} from "react-spinners";
const Loader = ({ loading, message }) => {
return loading ? (
<div className='wrapper'>
<ClipLoader
size={40}
color={"#123abc"}
loading={loading}
/>
<span className='message'>
{message}
</span>
</div>
) : null
};
export default Loader;
In Actions:
export const GET_SERVICE = '[SERVICE REQUEST] GET LIST';
export const GET_SERVICE_SUCCESS = '[SERVICE REQUEST] GET LIST SUCCESS';
export const GET_SERVICE_FAILURE = '[SERVICE REQUEST] GET LIST FAILURE';
export function getService(options) {
const request = axios.post('api/services', {options});
return (dispatch) => {
dispatch({
type: GET_SERVICE
});
try {
request.then((response) =>
dispatch({
type: GET_SERVICE_SUCCESS,
payload: {data: response.data.data, meta: response.data.meta}
})
);
} catch (e) {
dispatch({
type: GET_SERVICE_FAILURE
});
}
}
}
In Reducer:
const initialState = {
services: [],
loading: false,
hasErrors: false,
};
const serviceReducer = function (state = initialState, action) {
switch (action.type) {
case Actions.GET_SERVICE:
return {...state, loading: true};
case Actions.GET_SERVICE_SUCCESS:
return {
...state,
loading: false,
services: [...action.payload.data],
page: action.payload.meta.page,
total: action.payload.meta.total
};
case Actions.GET_SERVICE_FAILURE:
return {...state, loading: false, hasErrors: true};
default:
return state;
}
};
export default serviceReducer;
Write following code in your component:
<div className="flex flex-1 w-full">
<Loader loading={loading} />
</div>