Why does my context not update state as expected? - reactjs

The imageList is empty even though I have used this.context.setImageList to update the state to get the data from the API. I am confused why the state just didn't update. I have been spending a lot of time on this and haven't found the root cause. I'd appreciate if you can guide me through it. Thank you!
Create context and attach it in the Provider
import React, { Component } from 'react';
const ImageListContext = React.createContext({
imageList: [],
error: null,
loading: true,
setError: () => {},
clearError: () => {},
setImageList: () => {},
})
export default ImageListContext
export class ImageListProvider extends Component {
state = {
imageList: [],
error: null,
};
setImageList = imageList => {
this.setState({imageList})
}
setError = error => {
console.error(error)
this.setState({ error })
}
clearError = () => {
this.setState({ error: null })
}
render() {
const value = {
imageList: this.state.imageList,
error: this.state.error,
setError: this.setError,
clearError: this.clearError,
setImageList: this.setImageList,
}
return (
<div>
{this.loading ? <div>Loading Images...</div> :
<ImageListContext.Provider value={value}>
{this.props.children}
</ImageListContext.Provider>}
</div>
)
}
}
Use Context to update imageList array with the data from API and get the data out of the array to display it
import React, { Component } from 'react'
import ImageApiService from '../../services/image-api-service'
import { Section } from '../../components/Utils/Utils'
import ImageListItem from '../../components/ImageListItem/ImageListItem'
import './ImageListPage.css'
import ImageListContext from '../../contexts/ImageListContext'
export default class ImageListPage extends Component {
static contextType = ImageListContext;
componentDidMount() {
//this calls the image API to get all images!
ImageApiService.getImages()
.then(resJson => this.context.setImageList(resJson))
.catch(error => console.log(error))
}
setError = error => {
console.error(error)
this.setState({ error })
}
clearError = () => {
this.setState({ error: null })
}
renderImages() {
const { imageList=[] } = this.context;
console.log(imageList)
return imageList.map(image =>
<ImageListItem
key={image.id}
image={image}
/>
)
}
render() {
return (
<Section list className='ImageListPage'>
{this.context.error
? <p className='red'>There was an error, try again</p>
: this.renderImages()}
</Section>
)
}
}

Related

Get data from server using Axios and MobX in React

I need to get the data from the server and save it somewhere so that after re-rendering the LoginsList component, I don't have to request the data from the server again. I decided to start using MobX, but the store function fetchData() just doesn't seem to get called.
For now, the data is accepted in raw form, but then I will use encryption.
store.js
import { makeObservable } from 'mobx';
class store {
data = []
isFetching = false
error = null
constructor() {
makeObservable(this, {
data: observable,
isFetching: observable,
error: observable,
fetchData: action
})
}
fetchData() {
this.isFetching = true
axios.get('http://localhost:3001/data')
.then(response => {
this.data = response.data
this.isFetching = false
console.log('Success');
})
.catch(err => {
this.error = err
this.isFetching = false
console.log('Error');
})
}
}
export default store;
LoginsList.js
import React, { useState, useEffect } from 'react';
import classNames from 'classnames';
import { Observer } from 'mobx-react-lite';
import store from '../.store/data';
import LoginDetails from './LoginDetails';
import Login_Icon from '../assets/icons/Login.svg'
import '../assets/css/LoginCards.css'
const LoginsList = () => {
const [activeTab, setActiveTab] = useState(0);
const [hoveredTab, setHoveredTab] = useState(null);
const handleMouseEnter = (index) => {
if (index !== activeTab) {
setHoveredTab(index);
}
}
const handleClick = (index) => {
setHoveredTab(null);
setActiveTab(index);
}
useEffect(() => {
store.fetchData();
}, []);
return (
<>
<Observer>
<ul>
{store.data.map((card, index) => (
<li
key={card.id}
onClick={() => handleClick(index)}
onMouseEnter={() => handleMouseEnter(index)}
onMouseLeave={() => setHoveredTab(null)}
className="LoginCard"
>
<div
className={classNames('LoginCardContainer', { 'active-logincard': index === activeTab }, { 'hovered-logincard': index === hoveredTab })}
>
<img src={Login_Icon} alt="Login Icon"></img>
<div className="TextZone">
<p>{card.name}</p>
<div>{card.username}</div>
</div>
</div>
</li>
))}
</ul>
<div>
<div className="LoginDetails">
<img className="LoginDetailsIcon" src={Login_Icon}></img>
</div>
<LoginDetails key={activeTab} name={store.data[activeTab].name} username={store.data[activeTab].username} password={store.data[activeTab].password}/>
{store.data[activeTab].password}
</div>
</Observer>
</>
);
}
export default LoginsList;
I tried creating a store, importing it into the LoginsList component and getting the data. In the browser console, I saw an error Uncaught TypeError: _store_data__WEBPACK_IMPORTED_MODULE_3__.default.data is undefined
If I open http://localhost:3001/data in my browser, I can easily read the data. I don't think the error is on the server side.
I solved the problem. All I had to do was use makeAutoObservable instead of makeObservable.
import { action, makeAutoObservable } from 'mobx';
import axios from 'axios';
class UserData {
data = []
isFetching = false
error = null
constructor() {
makeAutoObservable(this)
}
fetchData() {
this.isFetching = true
axios.get('http://localhost:3001/data')
.then(response => {
this.data = response.data
this.isFetching = false
console.log('Success');
})
.catch(err => {
this.error = err
this.isFetching = false
console.log('Error');
})
};
}
export default new UserData;

Props not displaying from fetch call

I am trying to display recipes and not sure if I have this setup correctly. I am pulling recipes from a rails api via get fetch request. At the moment nothing is displaying.
Here is my recipe container:
import React, { Component } from 'react'
import RecipeList from '../components/RecipeList'
import RecipeInput from '../components/RecipeInput'
import { connect } from 'react-redux'
import { postRecipes } from '../actions/postRecipes.js'
import { getRecipes } from '../actions/getRecipes'
class RecipeContainer extends Component{
constructor(props){
super(props)
}
componentDidMount(){
getRecipes()
}
render(){
return (
<div>
<RecipeInput postRecipes={this.props.postRecipes} />
<RecipeList getRecipes={this.props.recipes} />
</div>
)
}
}
const mapStateToProps = state =>({
recipes: state.recipes
})
const mapDispatchToProps = dispatch =>{
return{
postRecipes: (recipe) => dispatch(postRecipes(recipe)),
getRecipes: () => dispatch(getRecipes())
// deleteRecipe: id => dispatch({type: 'Delete_Recipe', id})
}
}
export default connect(mapStateToProps,mapDispatchToProps)(RecipeContainer)
Here is my get request....notice that I am returning my Recipe component here.
export const getRecipes = () => {
const BASE_URL = `http://localhost:10524`
const RECIPES_URL =`${BASE_URL}/recipes`
return (dispatch) => {
dispatch({ type: 'START_FETCHING_RECIPES_REQUEST' });
fetch(RECIPES_URL)
.then(response =>{ return response.json()})
.then(recipes => dispatch({ type: 'Get_Recipes', recipes }));
};
}
This is where I am trying to render the Recipe component from the get request
import React, {Component} from 'react';
// import { getRecipes } from '../actions/getRecipes.js';
import Recipe from './Recipe.js'
class RecipeList extends Component {
// componentDidMount(){
// getRecipes()
// }
render() {
return (
<div>
{this.props.recipes.map(recipe => (<Recipe recipe={recipe} key={recipe.id} />))}
</div>
)
}
}
export default RecipeList;
Edit: Added reducer
switch(action.type){
case 'Add_Recipe':
const recipe = {
name: action.name,
ingredients: action.ingredients,
chef_name: action.chef_name,
origin: action.origin,
category: action.category
}
return{
...state,
recipes: [...state.recipes, recipe],
}
case 'START_FETCHING_RECIPES_REQUEST':
return {
...state,
recipes: [...state.recipes],
requesting: true
}
case 'Get_Recipes':
return {
...state, recipes: action.recipes,
requesting: false
}
default:
return state
}
}
How can I correct this to make it work?
Issue
You are not passing the recipes to the RecipeList component that were fetched and presumably stored in state, and fed back to the UI via RecipeContainer.
Solution
Pass the recipe state from RecipeContainer to RecipeList as a prop. and then render/map the recipes from props.
RecipeContainer
class RecipeContainer extends Component{
componentDidMount() {
getRecipes();
}
render() {
return (
<div>
<RecipeInput postRecipes={this.props.postRecipes} />
<RecipeList getRecipes={this.props.recipes} /> // <-- pass recipe state
</div>
)
}
}
const mapStateToProps = state => ({
recipes: state.recipes,
});
const mapDispatchToProps = dispatch => {
return {
postRecipes: (recipe) => dispatch(postRecipes(recipe)),
getRecipes: () => dispatch(getRecipes())
}
};
RecipeList
class RecipeList extends Component {
render() {
const { recipes } = this.props;
return (
<div>
{recipes.map(recipe => (
<Recipe recipe={recipe} key={recipe.id} />
))}
</div>
);
}
}
The actual solution to this was I needed to have an explicit return in my mapStateToProp function.
Eg.
const mapStateToProp = state =>{
return {
recipes: state.recipes
}
}

React TypeError: this.props.message.map is not a function

I am trying to display messages on the screen which I receive from api. I checked in the debugger (here are the screenshots https://ibb.co/gShTG8g https://ibb.co/dQmfwJp) where all the stages are going fine, but in the end I get an error called TypeError: this.props.message.map is not a function. Here is my actual code, link to api https://rapidapi.com/ajith/api/messages
Messages.jsx
import React from "react";
export class Messages extends React.Component {
render() {
const MessageList = this.props.message.map((item, index) => {
return <div key={index}>
<p>{item.Message}</p>
</div>
});
return(
<div>
{MessageList}
</div>
);
}
}
MessagesContainer.js
import React from 'react';
import { connect } from 'react-redux';
import {Messages} from "./Messages";
import {getMessagesThunk} from "../../Redux/users-reducer";
class MessagesContainer extends React.Component {
componentDidMount() {
this.props.getMessagesThunk();
}
render() {
return(
<>
<Messages {...this.props} />
</>
)
}
}
let mapStateToProps = (state) => ({
message: state.usersPage.messages
})
export default connect(mapStateToProps, {getMessagesThunk})(MessagesContainer);
users-reducer.js (Here is a part of my code)
let initialState = {
messages: [],
};
case MESSAGE:
return {
...state, messages: action.messages
}
export const getMessage = (messages) => ({type: MESSAGE, messages})
export const getMessagesThunk = (messages) => {
return (dispatch) => {
usersAPI.message(messages).then(response => {
if(response.data) {
dispatch(getMessage(response.data.Message))
}
})
}
}
Api.js
import axios from "axios";
const instance = axios.create({
params: {category: 'love'},
withCredentials: true,
headers: {
"API-KEY": "6bec01a1-e00c-42ca-ab9d-a03ad2e730cc",
'x-rapidapi-key': 'bf490d72a0msh3bf159a87e0c27fp107a51jsn062ca1b9b00e',
'x-rapidapi-host': 'ajith-messages.p.rapidapi.com'
}
})
export const usersAPI = {
message() {
return instance.get(`https://ajith-messages.p.rapidapi.com/getMsgs`)
},
}
This is because the message is not an array you can loop through. It's a simple string.

Pass value from a component to context, and use the value in componentDidMount() method

I get the pathname in the WorldPage component and pass this value to the context.jsx in which I want to request data using the pathname.
However, I cannot get the correct value in the componentDidMount() method.
console.log(this.state.tab) should be /world, but still /home.
import axios from "axios";
export const Context = React.createContext();
export class Provider extends Component {
state = {
news_list: [],
tab: "/home",
tabChange: (tabName) => {
if (this.state.tab !== tabName) {
this.setState({
tab: tabName,
});
}
},
};
componentDidMount() {
console.log(this.state.tab);
axios
.get(this.state.tab)
.then((res) => {
console.log(res.data);
this.setState({
news_list: res.data,
});
// console.log(this.state.news_list);
})
.catch((err) => console.log(err));
}
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
export const Consumer = Context.Consumer;
import React, { Component } from "react";
import News from "../News/News";
import { Consumer } from "../../context";
export default class WorldPage extends Component {
render() {
const tabName = window.location.pathname;
return (
<Consumer>
{(value) => {
const { tabChange } = value;
tabChange(tabName);
console.log(tabName);
return (
<React.Fragment>
<News />
</React.Fragment>
);
}}
</Consumer>
);
}
}

Why can't I access the context's methods or properties

I am trying to use React's context api to manage a global state. When I try to invoke contextual methods or access contextual proprties, I get errors saying "this.context.setUser function does not exist" or "undefined".
I have however been able to hard code values into the state of the context and retreive the hardcoded value.
Feed Context
import React, { Component } from 'react'
const FeedContext = React.createContext({
Feed: [],
user: '',
error: null,
setError: () => {},
clearError: () => {},
setFeed: () => {},
setUser: () => {}
})
export default FeedContext
export class FeedProvider extends Component {
state = {
feed: [],
error: null,
user: ''
};
setUser = user => {
this.setState({ user })
}
setFeed = Feed => {
this.setState({ Feed })
}
setError = error => {
console.error()
this.setState({ error })
}
clearError = () => {
console.log('context is accessed')
this.setState({ error: null })
}
render() {
const value = {
feed: this.state.feed,
error: this.state.error,
setError: this.setError,
clearError: this.clearError,
setFeed: this.setFeed,
setUser: this.setUser
}
return (
<FeedContext.Provider value={value}>
{this.props.children}
</FeedContext.Provider>
)
}
}
AccountPanel.js
import React from 'react';
import FeedContext from "../../contexts/FeedContext";
// functional component
class AccountPanel extends React.Component {
static contextType = FeedContext
renderUserInfo(){
const { user = [] } = this.context;
//this returns "undefined"
console.log(user.user)
//this returns "user.setUser() is not a function"
user.setUser('newUser')
//this returns ' '
this.context.setUser('y')
console.log(user)
}
render(){
return (
<section>
{ this.renderUserInfo() }
AccountPanel
</section>
)
}
}
export default AccountPanel;
I would like to be able to update the contextual state/user via this.context.setUser('newUser), then consume that value in my navbar component
File App.js
import React, { Component } from 'react';
import AccountPanel from "./components/AccountPanel";
import { FeedProvider } from './components/FeedContext';
class App extends Component {
render() {
return (
<div className="App">
<FeedProvider>
<AccountPanel />
</FeedProvider>
</div>
);
}
}
export default App;
File : FeedContext.js
import React, { Component } from 'react'
const FeedContext = React.createContext({
Feed: [],
user: '',
error: null,
setError: () => {},
clearError: () => {},
setFeed: () => {},
setUser: () => {}
})
export default FeedContext
export class FeedProvider extends Component {
constructor(props){
super(props);
this.state = {
feed: [],
error: null,
user: "11"
};
}
setUser = user => {
console.log(`setting usr fns called for username: ${user}`);
this.setState({ user });
}
setFeed = Feed => {
this.setState({ Feed })
}
setError = error => {
console.error()
this.setState({ error })
}
clearError = () => {
console.log('context is accessed')
this.setState({ error: null })
}
componentDidMount(){
console.log('FeedProvider:componentDidMount');
}
render() {
let value1 = {
Feed:this.state.feed,
user:this.state.user,
error:this.state.error,
setError:this.setError,
clearError:this.clearError,
setFeed:this.setFeed,
setUser:this.setUser
}
return (
<FeedContext.Provider value={value1}>
{this.props.children}
</FeedContext.Provider>
)
}
}
File : AccountPanel.js
import React from 'react';
import FeedContext from "./FeedContext";
// functional component
class AccountPanel extends React.Component {
static contextType = FeedContext
// return BlogPost component html/(JSX)
componentDidMount(){
console.log('AccountPanel:componentDidMount');
console.log(this.context);
const value = this.context;
//this returns "undefined"
console.log(value.user)
//this returns "user.setUser() is not a function"
console.log(value.setUser);
value.setUser('newUser');
}
render(){
const value = this.context;
console.log(`Value of new User is : ${value.user}`);
return (
<section>
AccountPanel
</section>
)
}
}
export default AccountPanel;
Hope This helps :)

Resources